Source code for mapstp.tree

"""Data structures and algorithms to store and process STP nodes and their links."""

from __future__ import annotations

from typing import TYPE_CHECKING, cast

from dataclasses import dataclass

from mapstp.exceptions import STPParserError
from mapstp.stp_parser import make_index

if TYPE_CHECKING:
    from collections.abc import Generator, Iterable, Iterator

    from mapstp.stp_parser import LeafProduct, Link, LinksList, Product


[docs] @dataclass class Node: """Node for the following `Tree` class. Stores the references to a `Product` corresponding to a number presented in a `Link` as source or destination. Also stores reference to its parent node. Only upward search is necessary in this application, so, there's no references to childes. """ product: Product parent: Node | None = None
[docs] def collect_parents(self: Node) -> Iterator[Product]: """Iterate through the parents of the node from root parent to this node. Yields: Chain of products starting from the topmost node. """ if self.parent is not None: yield from self.parent.collect_parents() yield self.product
[docs] class Tree: """Upward directed tree: it is used to find only the parents from a given node. Indexes and stores results of STP file parsing. """
[docs] def __init__(self: Tree, products: Iterable[Product], links: LinksList) -> None: """Create tree from objects found in an STP file. Args: products: list of product found on parsing STP links: pairs denoting links between the products. """ self._product_index: dict[int, Product] = make_index(products) self._node_index: dict[int, Node] = {} self._body_links: LinksList = [] for link in links: self._create_nodes_from_link(link)
[docs] def create_bodies_paths(self: Tree) -> list[str]: """Create list of paths for each body in STP file. Returns: The list of paths. """ def _scan() -> Generator[str]: for link in self._body_links: src, dst = link.src, link.dst product = self._product_index[dst] if product.is_leaf: node = self._node_index[src] path: list[str] = [parent.name for parent in node.collect_parents()] path.append(product.name) for b in cast("LeafProduct", product).bodies: yield "/".join([*path, b.name]) return list(_scan())
def _create_nodes_from_link(self: Tree, link: Link) -> None: src, dst = link.src, link.dst product = self._product_index[dst] parent = self._node_index.get(src) if parent is None: parent = self._create_node(self._product_index[src]) if product.is_leaf: self._body_links.append(link) else: self._add_or_update_intermediate_node(dst, parent, product) def _add_or_update_intermediate_node( self: Tree, dst: int, parent: Node, product: Product, ) -> None: node = self._node_index.get(dst) if node is None: self._create_node(product, parent) else: if node.parent is not None: # pragma: no cover raise STPParserError node.parent = parent def _create_node(self: Tree, product: Product, parent: Node | None = None) -> Node: """Create and register a node. Args: product: data associated with the new Node parent: its parent Node Returns: new Node """ node = Node(product, parent) self._node_index[product.number] = node return node
[docs] def create_bodies_paths(products: Iterable[Product], links: LinksList) -> list[str]: """Create list of paths for each body in STP file. Args: products: list of product found on parsing STP links: pairs denoting links between the products. Returns: The list of paths. Raises: ValueError: if more than one product is found in STP without components """ if links: tree = Tree(products, links) return tree.create_bodies_paths() ps = list(products) if len(ps) != 1: # pragma: no cover msg = "Only one product is expected for `simple` stp" raise ValueError(msg) product = cast("LeafProduct", ps[0]) return [b.name for b in product.bodies]