|
6 | 6 | import re |
7 | 7 | from collections import namedtuple, defaultdict |
8 | 8 | from functools import reduce |
9 | | -from typing import Optional, Generator, Any, Dict, List, Set, Tuple, Union, Iterator |
| 9 | +from typing import Optional, Generator, Any, Dict, List, Set, Tuple, Union, Iterator, DefaultDict |
10 | 10 |
|
11 | 11 | from attrs import define |
12 | 12 | from networkx import DiGraph, MultiDiGraph, is_directed_acyclic_graph |
@@ -476,40 +476,34 @@ def resolve(self) -> None: |
476 | 476 | log.info("Resolve attributes finished.") |
477 | 477 |
|
478 | 478 | def __resolve_count_descendants(self) -> None: |
479 | | - visited: Set[str] = set() |
480 | 479 | empty_set: Set[str] = set() |
481 | 480 |
|
482 | | - def count_successors_by(node_id: NodeId, edge_type: EdgeType, path: List[str]) -> Dict[str, int]: |
483 | | - result: Dict[str, int] = {} |
484 | | - to_visit = list(self.successors(node_id, edge_type)) |
485 | | - while to_visit: |
486 | | - visit_next: List[NodeId] = [] |
487 | | - for elem_id in to_visit: |
488 | | - if elem_id not in visited: |
489 | | - visited.add(elem_id) |
490 | | - elem = self.nodes[elem_id] |
491 | | - if "phantom" not in elem.get("kinds_set", empty_set): |
492 | | - extracted = value_in_path(elem, path) |
493 | | - if isinstance(extracted, str): |
494 | | - result[extracted] = result.get(extracted, 0) + 1 |
495 | | - # check if there is already a successor summary: stop the traversal and take the result. |
496 | | - existing = value_in_path(elem, NodePath.descendant_summary) |
497 | | - if existing and isinstance(existing, dict): |
498 | | - for summary_item, count in existing.items(): |
499 | | - result[summary_item] = result.get(summary_item, 0) + count |
500 | | - else: |
501 | | - visit_next.extend(a for a in self.successors(elem_id, edge_type) if a not in visited) |
502 | | - to_visit = visit_next |
| 481 | + def count_descendants_of(identifier: str, ancestor_kind: str, path: List[str]) -> Dict[str, int]: |
| 482 | + result: DefaultDict[str, int] = defaultdict(int) |
| 483 | + ancestor_path = ["ancestors", ancestor_kind, "reported", "id"] |
| 484 | + for _, elem in self.g.nodes(data=True): |
| 485 | + if value_in_path(elem, ancestor_path) == identifier: |
| 486 | + kinds_set = elem.get("kinds_set", empty_set) |
| 487 | + extracted = value_in_path(elem, path) |
| 488 | + if "phantom_resource" not in kinds_set and isinstance(extracted, str): |
| 489 | + result[extracted] += 1 |
503 | 490 | return result |
504 | 491 |
|
505 | 492 | for on_kind, prop in GraphResolver.count_successors.items(): |
506 | | - for node_id, node in self.g.nodes(data=True): |
| 493 | + for _, node in self.g.nodes(data=True): |
507 | 494 | kinds = node.get("kinds_set") |
508 | 495 | if kinds and on_kind in kinds: |
509 | | - summary = count_successors_by(node_id, EdgeTypes.default, prop.extract_path) |
510 | | - set_value_in_path(summary, prop.to_path, node) |
511 | | - total = reduce(lambda left, right: left + right, summary.values(), 0) |
512 | | - set_value_in_path(total, NodePath.descendant_count, node) |
| 496 | + if rid := value_in_path(node, NodePath.reported_id): |
| 497 | + # descendant summary |
| 498 | + summary = count_descendants_of(rid, on_kind, prop.extract_path) |
| 499 | + set_value_in_path(summary, prop.to_path, node) |
| 500 | + # descendant count |
| 501 | + total = reduce(lambda left, right: left + right, summary.values(), 0) |
| 502 | + set_value_in_path(total, NodePath.descendant_count, node) |
| 503 | + # update hash |
| 504 | + node["hash"] = GraphBuilder.content_hash( |
| 505 | + node["reported"], node.get("desired"), node.get("metadata") |
| 506 | + ) |
513 | 507 |
|
514 | 508 | def __resolve(self, node_id: NodeId, node: Json) -> Json: |
515 | 509 | def with_ancestor(ancestor: Json, prop: ResolveProp) -> None: |
|
0 commit comments