def test_value_in_path() -> None: js = {"foo": {"bla": {"test": 123}}} assert value_in_path(js, ["foo", "bla", "test"]) == 123 assert value_in_path_get( js, ["foo", "bla", "test"], "foo") == "foo" # expected string got int -> default value assert value_in_path(js, ["foo", "bla", "test", "bar"]) is None assert value_in_path_get(js, ["foo", "bla", "test", "bar"], 123) == 123 assert value_in_path(js, ["foo", "bla", "bar"]) is None assert value_in_path_get(js, ["foo", "bla", "bar"], "foo") == "foo"
def count_successors_by(node_id: str, edge_type: str, path: List[str]) -> Dict[str, int]: result: Dict[str, int] = {} to_visit = list(self.successors(node_id, edge_type)) while to_visit: visit_next: List[str] = [] for elem_id in to_visit: if elem_id not in visited: visited.add(elem_id) elem = self.nodes[elem_id] if not value_in_path_get(elem, NodePath.is_phantom, False): extracted = value_in_path(elem, path) if isinstance(extracted, str): result[extracted] = result.get(extracted, 0) + 1 # check if there is already a successor summary: stop the traversal and take the result. existing = value_in_path(elem, NodePath.descendant_summary) if existing and isinstance(existing, dict): for summary_item, count in existing.items(): result[summary_item] = result.get( summary_item, 0) + count else: visit_next.extend( a for a in self.successors(elem_id, edge_type) if a not in visited) to_visit = visit_next return result
def no_nested_props(js: Json) -> Json: reported: Json = value_in_path_get(js, NodePath.reported, {}) res = { k: v for k, v in reported.items() if v is not None and not isinstance(v, (dict, list)) } return res
async def respond_cytoscape( gen: AsyncIterator[JsonElement]) -> AsyncGenerator[str, None]: # Note: this is a very inefficient way of creating a response, since it creates the graph in memory # on the server side, so we can reuse the networkx code. # This functionality can be reimplemented is a streaming way. graph = await result_to_graph( gen, lambda js: value_in_path_get(js, NodePath.reported, {})) yield json.dumps(cytoscape_data(graph))
def render_dot(gen: Iterator[JsonElement]) -> Generator[str, None, None]: # We use the paired12 color scheme: https://graphviz.org/doc/info/colors.html with color names as 1-12 cit = count_iterator() icon_map = generate_icon_map() colors: Dict[str, int] = defaultdict(lambda: (next(cit) % 12) + 1) node = "node [shape=plain colorscheme=paired12]" edge = "edge [arrowsize=0.5]" yield render_dot_header(node, edge) in_account: Dict[str, List[str]] = defaultdict(list) for item in gen: if isinstance(item, dict): type_name = item.get("type") if type_name == "node": uid = value_in_path(item, NodePath.node_id) if uid: name = value_in_path_get(item, NodePath.reported_name, "n/a") kind = value_in_path_get(item, NodePath.reported_kind, "n/a") account = value_in_path_get(item, NodePath.ancestor_account_name, "graph_root") id = value_in_path_get(item, NodePath.reported_id, "n/a") parsed_kind = parse_kind(kind) paired12 = kind_colors.get(parsed_kind, colors[kind]) in_account[account].append(uid) resource = ResourceDescription(uid, name, id, parsed_kind, kind) yield render_resource(resource, icon_map, paired12) elif type_name == "edge": from_node = value_in_path(item, NodePath.from_node) to_node = value_in_path(item, NodePath.to_node) if from_node and to_node: yield f' "{from_node}" -> "{to_node}" ' else: raise AttributeError( f"Expect json object but got: {type(item)}: {item}") # All elements in the same account are rendered as dedicated subgraph for account, uids in in_account.items(): yield f' subgraph "{account}" {{' for uid in uids: yield f' "{uid}"' yield " }" yield "}"
async def respond_dot( gen: AsyncIterator[JsonElement]) -> AsyncGenerator[str, None]: # We use the paired12 color scheme: https://graphviz.org/doc/info/colors.html with color names as 1-12 cit = count_iterator() colors: Dict[str, int] = defaultdict(lambda: (next(cit) % 12) + 1) node = "node [shape=Mrecord colorscheme=paired12]" edge = "edge [arrowsize=0.5]" yield f"digraph {{\nrankdir=LR\noverlap=false\nsplines=true\n{node}\n{edge}" in_account: Dict[str, List[str]] = defaultdict(list) async for item in gen: if isinstance(item, dict): type_name = item.get("type") if type_name == "node": uid = value_in_path(item, NodePath.node_id) if uid: name = re.sub( "[^a-zA-Z0-9]", "", value_in_path_get(item, NodePath.reported_name, "n/a")) kind = value_in_path_get(item, NodePath.reported_kind, "n/a") account = value_in_path_get(item, NodePath.ancestor_account_name, "graph_root") paired12 = colors[kind] in_account[account].append(uid) yield f' "{uid}" [label="{name}|{kind}", style=filled fillcolor={paired12}];' elif type_name == "edge": from_node = value_in_path(item, NodePath.from_node) to_node = value_in_path(item, NodePath.to_node) edge_type = value_in_path(item, NodePath.edge_type) if from_node and to_node: yield f' "{from_node}" -> "{to_node}" [label="{edge_type}"]' else: raise AttributeError( f"Expect json object but got: {type(item)}: {item}") # All elements in the same account are rendered as dedicated subgraph for account, uids in in_account.items(): yield f' subgraph "{account}" {{' for uid in uids: yield f' "{uid}"' yield " }" yield "}"
def resolved_kind(node: Json) -> Optional[str]: kinds: List[str] = value_in_path_get(node, NodePath.kinds, []) for kind in kinds: if kind in GraphResolver.resolved_ancestors: return kind return None