pyracmon.graph.graph API documentation
Description: This module exports types representing graphs.

# Module `pyracmon.graph.graph`

This module exports types representing graphs.

Expand source code

    """
    This module exports types representing graphs.
    """
    from typing import TypeVar, Generic, Protocol, Union, Optional, Any, overload, cast
    from typing_extensions import Self
    from collections.abc import MutableMapping, Iterable, Iterator
    from typing import Any
    from .identify import IdentifyPolicy, neverPolicy
    from .template import GraphTemplate
    from .protocol import *

    T = TypeVar('T', covariant=True)

    class GraphView(Protocol):
        """
        The interface of the view of graph.
        """
        def __call__(self) -> 'Graph': ...
        def __iter__(self) -> Iterator[tuple[str, 'ContainerView[NodeContainer]']]:
            """
            Iterates root container views.

            Returns:
                Iterator of pairs of name and container view.
            """
            ...
        def __getattr__(self, name: str) -> 'ContainerView':
            """
            Returns a container view by its name.

            Args:
                name: Container name. i.e. template property name for the node container.
            Returns:
                Container view.
            """
            ...

    class Graph:
        """
        This class represents a graph composed of tree-structured node containers.

        The structure is determined by `GraphTemplate`. Use `new_graph` Instead of constructor to create new graph instance.

        ```python
        template = GraphSpac().new_template(
            a = (int, lambda x:x),
            b = (str, lambda x:x),
            c = (str, lambda x:x),
        )
        template.a << template.b << template.c
        graph = new_graph(template)
        ```

        In above code, a graph which has 3 properties ( `a` `b` `c` ) and a structure where `a` is parent of `b` and `b` is parent of `c` is created.

        `append` ( `replace` ) is a method to store entities in the graph with tying them each other according to the structure.
        Entites are encapsulated by `Node` which can have an edge to parent node.

        ```python
        graph.append(a=1, b="a", c="x").append(a=2, b="b", c="y")
        ```

        In `append`, entities are first sorted in descending order, and then:

        - Search a node whose entity is *identical* to the first entity from the corresponding node container.
            - If found, new node is not created and the *identical* node is set to next parent.
            - Otherwise, new node is appended and it is set to next parent.
        - Apply this to following entities in order. A difference is that *identical* node is searched from the sequence of parents in the session.

        In example here, the identification is done by entity value itself ( `lambda x:x` ). Next code is the example where *identical* nodes are found.

        ```python
        graph.append(a=1, b="a", c="z").append(a=2, b="c", c="y")
        ```

        In the first `append`, `a` and `b` has its *identical* node and `a` is *identical* in the second.
        `c` in the second one is not *identical* to any node because parent node `b="c"` is already added as new node.

        Due to the identification mechanism, entity relationships in the graph is guaranteed after repeating `append` .
        """
        def __init__(self, template: GraphTemplate):
            #: Graph template.
            self.template: GraphTemplate = template
            #: A `dict` containing node containers by their names.
            self.containers: dict[str, NodeContainer] = {p.name:self._to_container(p) for p in template}
            self._view = None

        def _to_container(self, prop: GraphTemplate.Property) -> 'NodeContainer':
            if isinstance(prop.kind, GraphTemplate):
                return _GraphNodeContainer(prop)
            else:
                return NodeContainer(prop)

        def _container_of(self, prop: GraphTemplate.Property) -> Optional['NodeContainer']:
            candidates = [c for c in self.containers.values() if c.prop.is_compatible(prop)]
            if len(candidates) > 1:
                raise ValueError(f"Container can't be determined from property '{prop.name}'.")
            return candidates[0] if candidates else None

        def __add__(self, another: Union[Self, GraphView]) -> 'Graph':
            """
            Create new graph by adding this graph and another graph.

            New graph has the same template as this graph's.
            On the other hand, because this method depends on `__iadd__()`, another graph must not have the same template.

            Args:
                another: Graph or its view.
            Returns:
                Created graph.
            """
            graph = Graph(self.template)

            graph += self
            graph += another

            return graph

        def __iadd__(self, another: Union[Self, GraphView]) -> Self:
            """
            Append nodes from another graph.

            Nodes of another graph are traversed from its root and appended to compatible containers each other.

            Args:
                another: Graph or its view.
            Returns:
                This graph.
            """
            graph = another if isinstance(another, Graph) else another()

            def add(n: Node, anc: dict[str, list[Node]]):
                c = self._container_of(n.prop)
                if c:
                    c.append(n.entity, anc)
                for ch_ in n.children.values():
                    for m in ch_.nodes:
                        add(m, anc.copy())

            for c_ in graph.roots:
                for n_ in c_.nodes:
                    add(n_, {})

            return self

        @property
        def roots(self) -> Iterable['NodeContainer']:
            """
            Returns root node containers.
            """
            return filter(lambda c: c.prop.parent is None, self.containers.values())

        @property
        def view(self) -> GraphView:
            """
            Returns an unmodifiable view of this graph.

            The view object works as the accessor to graph nodes.

            ```python
            >>> template = GraphSpac().new_template(a=int, b=str, c=str)
            >>> template.a << template.b
            >>> graph = new_graph(template)
            >>> view = graph.view
            >>> assert view() is graph                        # invocation
            >>> assert view.a is graph.containers["a"].view   # attribute
            >>> assert [c().name for c in view] == ["a", "c"] # iteration
            ```
            """
            if self._view is None:
                graph = self
                class _GraphView:
                    def __call__(self) -> Graph:
                        """Returns the greph of this view."""
                        return graph
                    def __iter__(self) -> Iterator[tuple[str, ContainerView[NodeContainer]]]:
                        """Iterates views of root containers."""
                        return map(lambda c: (c.name, c.view), filter(lambda c: c.prop.parent is None, graph.containers.values()))
                    def __getattr__(self, name: str) -> ContainerView:
                        """Returns a view of a container of the name."""
                        return graph.containers[name].view
                self._view = _GraphView()
            return self._view

        def _append(self, to_replace: bool, entities: dict[str, Any]) -> Self:
            props = [p for p in self.template if p.name in entities]

            filtered = set()
            for p in props:
                if (p.parent is None) or (p.parent.name not in entities) or (p.parent.name in filtered):
                    if p.entity_filter is None or p.entity_filter(entities[p.name]):
                        filtered.add(p.name)

            ancestors = {}
            for k in [p.name for p in props if p.name in filtered]:
                self.containers[k].append(entities[k], ancestors, to_replace)

            return self

        def append(self, **entities: Any) -> Self:
            """
            Append entities with associated property names.

            Args:
                entities: Entities keyed with associated property names.
            Returns:
                This graph.
            """
            return self._append(False, entities)

        def replace(self, **entities: Any) -> Self:
            """
            Works similarly to `append`, but entities of identical nodes are replaced with given entities.

            Args:
                entities: Entities keyed with associated property names.
            Returns:
                This graph.
            """
            return self._append(True, entities)

    def new_graph(template: GraphTemplate, *bases: Union[Graph, GraphView]) -> Graph:
        """
        Create a graph from a template.

        Use this function instead of invoking constructor directly.

        Args:
            template: A template of a graph.
            bases: Other graphs whose nodes are appended to created graph.
        Returns:
            Created graph.
        """
        graph = Graph(template)

        for b in bases:
            graph += b

        return graph

    class ContainerView(Protocol, Generic[T]):
        """
        The interface of the view of a node set, i.e. `NodeContainer` and `Node.Children` .
        """
        def __bool__(self) -> bool:
            """Returns whether this container is not empty."""
            ...
        def __call__(self) -> T:
            """Returns a base container."""
            ...
        def __len__(self) -> int:
            """Returns the number of nodes."""
            ...
        def __iter__(self) -> Iterator['NodeView']:
            """Iterates views of nodes."""
            ...
        @overload
        def __getitem__(self, index: int) -> 'NodeView': ...
        @overload
        def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
        def __getitem__(self, index: Union[int, slice]) -> Union['NodeView', Iterable['NodeView']]:
            """Returns a view of a node at the index."""
            ...
        def __getattr__(self, key) -> 'ContainerView':
            """Returns a view of the first node or empty container view if it does not exist."""
            ...

    class _EmptyContainerView(ContainerView[None]):
        def __init__(self, prop):
            self.prop = prop

        def __bool__(self):
            return False

        def __call__(self) -> None:
            return None

        def __iter__(self):
            return iter([])

        def __len__(self):
            return 0

        def __getitem__(self, index):
            if isinstance(index, slice):
                return []
            else:
                raise IndexError(f"Index for container '{self.prop.name}' is out of range.")

        def __getattr__(self, key):
            child = next(filter(lambda c: c.name == key, self.prop.children), None)
            if child:
                return _EmptyContainerView(child)
            else:
                raise KeyError(f"Graph property '{self.prop.name}' does not have a child property '{key}'.")

    class NodeContainer:
        """
        This class represents a container of nodes for a template property.
        """
        def __init__(self, prop: GraphTemplate.Property):
            #: Template property.
            self.prop = prop
            self.nodes: list[Node] = []
            self.keys: dict[Any, list[int]] = {}
            self._view = None

        @property
        def name(self) -> str:
            """
            Returns the container name, which is same as the name of template property.
            """
            return self.prop.name

        @property
        def view(self) -> ContainerView['NodeContainer']:
            """
            Returns an unmodifiable view of this container.

            The view object works as the accessor to container components.

            ```python
            template = GraphSpac().new_template(a=int, b=str, c=str)
            template.a << template.b
            graph = new_graph(template).append(a=1, b="a").append(a=1, b="b").append(a=2, b="c")
            container = graph.containers["a"]
            view = graph.view.a
            assert view() is container                             # invocation
            assert view.b is container.nodes[0].children["b"].view # attribute
            assert view[1] is container.nodes[1].view              # index
            assert [n() for n in view] == [1, 2]                   # iteration
            assert len(view) == 2                                  # length
            ```
            """
            if self._view is None:
                container = self
                class _ContainerView:
                    def __bool__(self):
                        """Returns whether this container is not empty."""
                        return len(container.nodes) != 0
                    def __call__(self) -> NodeContainer:
                        """Returns a base container."""
                        return container
                    def __len__(self):
                        """Returns the number of nodes."""
                        return len(container.nodes)
                    def __iter__(self):
                        """Iterates views of nodes."""
                        return map(lambda n: n.view, container.nodes)
                    @overload
                    def __getitem__(self, index: int) -> 'NodeView': ...
                    @overload
                    def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
                    def __getitem__(self, index: Union[int, slice]) -> Union['NodeView', Iterable['NodeView']]:
                        """Returns a view of a node at the index."""
                        if isinstance(index, slice):
                            return [n.view for n in container.nodes[index]]
                        else:
                            return container.nodes[index].view
                    def __getattr__(self, key) -> ContainerView:
                        """Returns a view of the first node or empty container view if it does not exist."""
                        child = next(filter(lambda c: c.name == key, container.prop.children), None)
                        if child:
                            return container.nodes[0].children[key].view if len(container.nodes) > 0 else _EmptyContainerView(child)
                        else:
                            raise KeyError(f"Graph property '{container.prop.name}' does not have a child property '{key}'.")
                self._view = _ContainerView()
            return self._view

        def append(self, entity: Any, ancestors: MutableMapping[str, list['Node']], to_replace: bool = False):
            """
            Add an entity to this container.

            Identical node is searched by examining whether this container already contains a node of the identical entity
            and its parent is found in `anscestors` .

            Args:
                entity: An entity to be stored in the node.
                ancestors: Parent nodes mapped by property names.
                to_replace: If `True`, the entity of identical node is replaced. Otherwise, it is not changed.
            """
            policy: IdentifyPolicy = self.prop.policy or neverPolicy()

            key = policy.get_identifier(entity)

            parents, identicals = policy.identify(self.prop, [self.nodes[i] for i in self.keys.get(key, [])], ancestors)

            new_nodes = identicals.copy()

            for pn in parents:
                index = len(self.nodes)

                node = Node(self.prop, entity, key, index)
                self.nodes.append(node)
                if key is not None:
                    self.keys.setdefault(key, []).append(index)
                new_nodes.append(node)

                if pn is not None:
                    pn.add_child(node)

            if to_replace:
                for n in identicals:
                    n.entity = entity

            ancestors[self.prop.name] = new_nodes

    class _GraphNodeContainer(NodeContainer):
        """
        NodeContainer which contains graph instances.
        """
        def append(self, entity: Any, ancestors: MutableMapping[str, Iterable['Node']], to_replace: bool = False):
            if not isinstance(entity, (dict, Graph)):
                raise ValueError(f"Node of graph only accepts dict or Graph object.")

            policy = self.prop.policy or neverPolicy()

            parents, _ = policy.identify(self.prop, cast(list[Node], []), ancestors)

            for pn in parents:
                index = len(self.nodes)

                graphs = []

                if pn is None or len(pn.children[self.name].nodes) == 0:
                    g = Graph(cast(GraphTemplate, self.prop.kind))
                    node = _GraphNode(self.prop, g, None, index)
                    self.nodes.append(node)

                    if pn is not None:
                        pn.add_child(node)

                    graphs.append(g)
                else:
                    graphs.extend([n.entity for n in pn.children[self.name].nodes])

                for g in graphs:
                    if isinstance(entity, dict):
                        g.append(**entity)
                    else:
                        g += entity

    class NodeView:
        def __call__(self, alt: Any = None) -> Any:
            """Returns an entity of this node."""
            ...
        def __getattr__(self, key: str) -> ContainerView:
            """Returns a view of child nodes by its name."""
            ...
        def __iter__(self) -> Iterator[tuple[str, ContainerView]]:
            """Iterate key-value pairs of child nodes."""
            ...

    class Node:
        """
        This class represents a node which contains an entity.
        """
        class Children:
            """
            This class represents a child nodes of a node.
            """
            def __init__(self, prop: GraphTemplate.Property):
                #: Template property.
                self.prop = prop
                self.nodes: list[Node] = []
                self.keys = set()
                self._view = None

            @property
            def name(self) -> str:
                """
                Returns the name of corresponding template property.
                """
                return self.prop.name

            @property
            def view(self) -> ContainerView['Node.Children']:
                """
                Returns an unmodifiable view of child nodes.
                """
                if self._view is None:
                    base = self
                    class _ChildrenView:
                        def __bool__(self):
                            """Returns whether this container is not empty."""
                            return len(base.nodes) != 0
                        def __call__(self):
                            """Returns children container."""
                            return base
                        def __iter__(self):
                            """Iterates views of child nodes."""
                            return map(lambda n: n.view, base.nodes)
                        def __len__(self):
                            """Returns the number of child nodes."""
                            return len(base.nodes)
                        @overload
                        def __getitem__(self, index: int) -> 'NodeView': ...
                        @overload
                        def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
                        def __getitem__(self, index):
                            """Returns a view of child node at the index."""
                            if isinstance(index, slice):
                                return [n.view for n in base.nodes[index]]
                            else:
                                return base.nodes[index].view
                        def __getattr__(self, key):
                            """Returns a view of the first node or empty container view if it does not exist."""
                            child = next(filter(lambda c: c.name == key, base.prop.children), None)
                            if child:
                                return base.nodes[0].children[key].view if len(base.nodes) > 0 else _EmptyContainerView(child)
                            else:
                                raise KeyError(f"Graph property '{base.prop.name}' does not have a child property '{key}'.")
                    self._view = _ChildrenView()
                return self._view

            def __contains__(self, node: 'Node') -> bool:
                return node in self.keys

            def __iter__(self) -> Iterator['Node']:
                return iter(self.nodes)

            def append(self, node):
                if node not in self.keys:
                    self.keys.add(node)
                    self.nodes.append(node)

        def __init__(self, prop: GraphTemplate.Property, entity: Any, key: Optional[Any], index: int):
            #: Template property.
            self.prop = prop
            #: An entity value.
            self.entity = entity
            self.key = key
            self.parents = set()
            self.children: dict[str, Node.Children] = {c.name: Node.Children(c) for c in prop.children}
            self._index = index
            self._view = None

        def __contains__(self, key: str) -> bool:
            return key in self.children

        @property
        def name(self) -> str:
            """
            Returns the container name, which is same as the name of template property.
            """
            return self.prop.name

        @property
        def view(self) -> NodeView:
            """
            Returns an unmodifiable view of this node.

            The view object works as the accessor to entity and child nodes.
            """
            if self._view is None:
                node = self
                class _NodeView(NodeView):
                    def __call__(self, alt: Any = None) -> Any:
                        """Returns an entity of this node."""
                        return node.entity
                    def __getattr__(self, key: str) -> ContainerView:
                        """Returns a view of child nodes by its name."""
                        return node.children[key].view
                    def __iter__(self) -> Iterator[tuple[str, ContainerView]]:
                        """Iterate key-value pairs of child nodes."""
                        return map(lambda nc: (nc[0], nc[1].view), node.children.items())
                self._view = _NodeView()
            return self._view

        def add_child(self, child: 'Node') -> Self:
            """
            Adds a child node.

            Args:
                child: Child node.
            Returns:
                This instance.
            """
            if child.prop.template != self.prop.template:
                raise ValueError(f"Nodes from different graph template can't be associated.")
            self.children[child.prop.name].append(child)
            child.parents.add(self)
            return self

        def has_child(self, child: 'Node') -> bool:
            """
            Checks this node contains the node identical to given node.

            Args:
                child: Node to search.
            Returns:
                `True` if exists.
            """
            if child.prop.template != self.prop.template:
                return False
            elif child.prop.name in self.children:
                return child in self.children[child.prop.name].keys
            else:
                return False

    class _GraphNode(Node):
        @property
        def view(self):
            return self.entity.view

        def add_child(self, child):
            raise TypeError(f"GraphNode does not have child.")

        def has_child(self, child):
            return False

## Functions

`def new_graph(template: GraphTemplate, *bases: Union[Graph, GraphView]) ‑> Graph`

  Create a graph from a template.

  Use this function instead of invoking constructor directly.

  ## Args

  `template`
    A template of a graph.
  `bases`
    Other graphs whose nodes are appended to created graph.

  ## Returns

  Created graph.

  Expand source code

      def new_graph(template: GraphTemplate, *bases: Union[Graph, GraphView]) -> Graph:
          """
          Create a graph from a template.

          Use this function instead of invoking constructor directly.

          Args:
              template: A template of a graph.
              bases: Other graphs whose nodes are appended to created graph.
          Returns:
              Created graph.
          """
          graph = Graph(template)

          for b in bases:
              graph += b

          return graph

## Classes

`class ContainerView (*args, **kwargs)`

  The interface of the view of a node set, i.e. `NodeContainer` and `Node.Children` .

  Expand source code

      class ContainerView(Protocol, Generic[T]):
          """
          The interface of the view of a node set, i.e. `NodeContainer` and `Node.Children` .
          """
          def __bool__(self) -> bool:
              """Returns whether this container is not empty."""
              ...
          def __call__(self) -> T:
              """Returns a base container."""
              ...
          def __len__(self) -> int:
              """Returns the number of nodes."""
              ...
          def __iter__(self) -> Iterator['NodeView']:
              """Iterates views of nodes."""
              ...
          @overload
          def __getitem__(self, index: int) -> 'NodeView': ...
          @overload
          def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
          def __getitem__(self, index: Union[int, slice]) -> Union['NodeView', Iterable['NodeView']]:
              """Returns a view of a node at the index."""
              ...
          def __getattr__(self, key) -> 'ContainerView':
              """Returns a view of the first node or empty container view if it does not exist."""
              ...

  ### Ancestors

  - typing.Protocol
  - typing.Generic

  ### Subclasses

  - pyracmon.graph.graph._EmptyContainerView
`class Graph (template: GraphTemplate)`

  This class represents a graph composed of tree-structured node containers.

  The structure is determined by `GraphTemplate`. Use `new_graph()` Instead of constructor to create new graph instance.

      template = GraphSpac().new_template(
          a = (int, lambda x:x),
          b = (str, lambda x:x),
          c = (str, lambda x:x),
      )
      template.a << template.b << template.c
      graph = new_graph(template)

  In above code, a graph which has 3 properties ( `a` `b` `c` ) and a structure where `a` is parent of `b` and `b` is parent of `c` is created.

  `append` ( `replace` ) is a method to store entities in the graph with tying them each other according to the structure. Entites are encapsulated by `Node` which can have an edge to parent node.

      graph.append(a=1, b="a", c="x").append(a=2, b="b", c="y")

  In `append`, entities are first sorted in descending order, and then:

  - Search a node whose entity is identical to the first entity from the corresponding node container.
    - If found, new node is not created and the identical node is set to next parent.
    - Otherwise, new node is appended and it is set to next parent.
  - Apply this to following entities in order. A difference is that identical node is searched from the sequence of parents in the session.

  In example here, the identification is done by entity value itself ( `lambda x:x` ). Next code is the example where identical nodes are found.

      graph.append(a=1, b="a", c="z").append(a=2, b="c", c="y")

  In the first `append`, `a` and `b` has its identical node and `a` is identical in the second. `c` in the second one is not identical to any node because parent node `b="c"` is already added as new node.

  Due to the identification mechanism, entity relationships in the graph is guaranteed after repeating `append` .

  Expand source code

      class Graph:
          """
          This class represents a graph composed of tree-structured node containers.

          The structure is determined by `GraphTemplate`. Use `new_graph` Instead of constructor to create new graph instance.

          ```python
          template = GraphSpac().new_template(
              a = (int, lambda x:x),
              b = (str, lambda x:x),
              c = (str, lambda x:x),
          )
          template.a << template.b << template.c
          graph = new_graph(template)
          ```

          In above code, a graph which has 3 properties ( `a` `b` `c` ) and a structure where `a` is parent of `b` and `b` is parent of `c` is created.

          `append` ( `replace` ) is a method to store entities in the graph with tying them each other according to the structure.
          Entites are encapsulated by `Node` which can have an edge to parent node.

          ```python
          graph.append(a=1, b="a", c="x").append(a=2, b="b", c="y")
          ```

          In `append`, entities are first sorted in descending order, and then:

          - Search a node whose entity is *identical* to the first entity from the corresponding node container.
              - If found, new node is not created and the *identical* node is set to next parent.
              - Otherwise, new node is appended and it is set to next parent.
          - Apply this to following entities in order. A difference is that *identical* node is searched from the sequence of parents in the session.

          In example here, the identification is done by entity value itself ( `lambda x:x` ). Next code is the example where *identical* nodes are found.

          ```python
          graph.append(a=1, b="a", c="z").append(a=2, b="c", c="y")
          ```

          In the first `append`, `a` and `b` has its *identical* node and `a` is *identical* in the second.
          `c` in the second one is not *identical* to any node because parent node `b="c"` is already added as new node.

          Due to the identification mechanism, entity relationships in the graph is guaranteed after repeating `append` .
          """
          def __init__(self, template: GraphTemplate):
              #: Graph template.
              self.template: GraphTemplate = template
              #: A `dict` containing node containers by their names.
              self.containers: dict[str, NodeContainer] = {p.name:self._to_container(p) for p in template}
              self._view = None

          def _to_container(self, prop: GraphTemplate.Property) -> 'NodeContainer':
              if isinstance(prop.kind, GraphTemplate):
                  return _GraphNodeContainer(prop)
              else:
                  return NodeContainer(prop)

          def _container_of(self, prop: GraphTemplate.Property) -> Optional['NodeContainer']:
              candidates = [c for c in self.containers.values() if c.prop.is_compatible(prop)]
              if len(candidates) > 1:
                  raise ValueError(f"Container can't be determined from property '{prop.name}'.")
              return candidates[0] if candidates else None

          def __add__(self, another: Union[Self, GraphView]) -> 'Graph':
              """
              Create new graph by adding this graph and another graph.

              New graph has the same template as this graph's.
              On the other hand, because this method depends on `__iadd__()`, another graph must not have the same template.

              Args:
                  another: Graph or its view.
              Returns:
                  Created graph.
              """
              graph = Graph(self.template)

              graph += self
              graph += another

              return graph

          def __iadd__(self, another: Union[Self, GraphView]) -> Self:
              """
              Append nodes from another graph.

              Nodes of another graph are traversed from its root and appended to compatible containers each other.

              Args:
                  another: Graph or its view.
              Returns:
                  This graph.
              """
              graph = another if isinstance(another, Graph) else another()

              def add(n: Node, anc: dict[str, list[Node]]):
                  c = self._container_of(n.prop)
                  if c:
                      c.append(n.entity, anc)
                  for ch_ in n.children.values():
                      for m in ch_.nodes:
                          add(m, anc.copy())

              for c_ in graph.roots:
                  for n_ in c_.nodes:
                      add(n_, {})

              return self

          @property
          def roots(self) -> Iterable['NodeContainer']:
              """
              Returns root node containers.
              """
              return filter(lambda c: c.prop.parent is None, self.containers.values())

          @property
          def view(self) -> GraphView:
              """
              Returns an unmodifiable view of this graph.

              The view object works as the accessor to graph nodes.

              ```python
              >>> template = GraphSpac().new_template(a=int, b=str, c=str)
              >>> template.a << template.b
              >>> graph = new_graph(template)
              >>> view = graph.view
              >>> assert view() is graph                        # invocation
              >>> assert view.a is graph.containers["a"].view   # attribute
              >>> assert [c().name for c in view] == ["a", "c"] # iteration
              ```
              """
              if self._view is None:
                  graph = self
                  class _GraphView:
                      def __call__(self) -> Graph:
                          """Returns the greph of this view."""
                          return graph
                      def __iter__(self) -> Iterator[tuple[str, ContainerView[NodeContainer]]]:
                          """Iterates views of root containers."""
                          return map(lambda c: (c.name, c.view), filter(lambda c: c.prop.parent is None, graph.containers.values()))
                      def __getattr__(self, name: str) -> ContainerView:
                          """Returns a view of a container of the name."""
                          return graph.containers[name].view
                  self._view = _GraphView()
              return self._view

          def _append(self, to_replace: bool, entities: dict[str, Any]) -> Self:
              props = [p for p in self.template if p.name in entities]

              filtered = set()
              for p in props:
                  if (p.parent is None) or (p.parent.name not in entities) or (p.parent.name in filtered):
                      if p.entity_filter is None or p.entity_filter(entities[p.name]):
                          filtered.add(p.name)

              ancestors = {}
              for k in [p.name for p in props if p.name in filtered]:
                  self.containers[k].append(entities[k], ancestors, to_replace)

              return self

          def append(self, **entities: Any) -> Self:
              """
              Append entities with associated property names.

              Args:
                  entities: Entities keyed with associated property names.
              Returns:
                  This graph.
              """
              return self._append(False, entities)

          def replace(self, **entities: Any) -> Self:
              """
              Works similarly to `append`, but entities of identical nodes are replaced with given entities.

              Args:
                  entities: Entities keyed with associated property names.
              Returns:
                  This graph.
              """
              return self._append(True, entities)

  ### Instance variables

  `var containers`

    A `dict` containing node containers by their names.

  `var roots : collections.abc.Iterable['NodeContainer']`

    Returns root node containers.

    Expand source code

        @property
        def roots(self) -> Iterable['NodeContainer']:
            """
            Returns root node containers.
            """
            return filter(lambda c: c.prop.parent is None, self.containers.values())

  `var template`

    Graph template.

  `var view : GraphView`

    Returns an unmodifiable view of this graph.

    The view object works as the accessor to graph nodes.

        >>> template = GraphSpac().new_template(a=int, b=str, c=str)
        >>> template.a << template.b
        >>> graph = new_graph(template)
        >>> view = graph.view
        >>> assert view() is graph                        # invocation
        >>> assert view.a is graph.containers["a"].view   # attribute
        >>> assert [c().name for c in view] == ["a", "c"] # iteration

    Expand source code

        @property
        def view(self) -> GraphView:
            """
            Returns an unmodifiable view of this graph.

            The view object works as the accessor to graph nodes.

            ```python
            >>> template = GraphSpac().new_template(a=int, b=str, c=str)
            >>> template.a << template.b
            >>> graph = new_graph(template)
            >>> view = graph.view
            >>> assert view() is graph                        # invocation
            >>> assert view.a is graph.containers["a"].view   # attribute
            >>> assert [c().name for c in view] == ["a", "c"] # iteration
            ```
            """
            if self._view is None:
                graph = self
                class _GraphView:
                    def __call__(self) -> Graph:
                        """Returns the greph of this view."""
                        return graph
                    def __iter__(self) -> Iterator[tuple[str, ContainerView[NodeContainer]]]:
                        """Iterates views of root containers."""
                        return map(lambda c: (c.name, c.view), filter(lambda c: c.prop.parent is None, graph.containers.values()))
                    def __getattr__(self, name: str) -> ContainerView:
                        """Returns a view of a container of the name."""
                        return graph.containers[name].view
                self._view = _GraphView()
            return self._view

  ### Methods

  `def append(self, **entities: Any) ‑> typing_extensions.Self`

    Append entities with associated property names.

    ## Args

    `entities`
      Entities keyed with associated property names.

    ## Returns

    This graph.

    Expand source code

        def append(self, **entities: Any) -> Self:
            """
            Append entities with associated property names.

            Args:
                entities: Entities keyed with associated property names.
            Returns:
                This graph.
            """
            return self._append(False, entities)

  `def replace(self, **entities: Any) ‑> typing_extensions.Self`

    Works similarly to `append`, but entities of identical nodes are replaced with given entities.

    ## Args

    `entities`
      Entities keyed with associated property names.

    ## Returns

    This graph.

    Expand source code

        def replace(self, **entities: Any) -> Self:
            """
            Works similarly to `append`, but entities of identical nodes are replaced with given entities.

            Args:
                entities: Entities keyed with associated property names.
            Returns:
                This graph.
            """
            return self._append(True, entities)

`class GraphView (*args, **kwargs)`

  The interface of the view of graph.

  Expand source code

      class GraphView(Protocol):
          """
          The interface of the view of graph.
          """
          def __call__(self) -> 'Graph': ...
          def __iter__(self) -> Iterator[tuple[str, 'ContainerView[NodeContainer]']]:
              """
              Iterates root container views.

              Returns:
                  Iterator of pairs of name and container view.
              """
              ...
          def __getattr__(self, name: str) -> 'ContainerView':
              """
              Returns a container view by its name.

              Args:
                  name: Container name. i.e. template property name for the node container.
              Returns:
                  Container view.
              """
              ...

  ### Ancestors

  - typing.Protocol
  - typing.Generic
`class Node (prop: GraphTemplate.Property, entity: Any, key: Optional[Any], index: int)`

  This class represents a node which contains an entity.

  Expand source code

      class Node:
          """
          This class represents a node which contains an entity.
          """
          class Children:
              """
              This class represents a child nodes of a node.
              """
              def __init__(self, prop: GraphTemplate.Property):
                  #: Template property.
                  self.prop = prop
                  self.nodes: list[Node] = []
                  self.keys = set()
                  self._view = None

              @property
              def name(self) -> str:
                  """
                  Returns the name of corresponding template property.
                  """
                  return self.prop.name

              @property
              def view(self) -> ContainerView['Node.Children']:
                  """
                  Returns an unmodifiable view of child nodes.
                  """
                  if self._view is None:
                      base = self
                      class _ChildrenView:
                          def __bool__(self):
                              """Returns whether this container is not empty."""
                              return len(base.nodes) != 0
                          def __call__(self):
                              """Returns children container."""
                              return base
                          def __iter__(self):
                              """Iterates views of child nodes."""
                              return map(lambda n: n.view, base.nodes)
                          def __len__(self):
                              """Returns the number of child nodes."""
                              return len(base.nodes)
                          @overload
                          def __getitem__(self, index: int) -> 'NodeView': ...
                          @overload
                          def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
                          def __getitem__(self, index):
                              """Returns a view of child node at the index."""
                              if isinstance(index, slice):
                                  return [n.view for n in base.nodes[index]]
                              else:
                                  return base.nodes[index].view
                          def __getattr__(self, key):
                              """Returns a view of the first node or empty container view if it does not exist."""
                              child = next(filter(lambda c: c.name == key, base.prop.children), None)
                              if child:
                                  return base.nodes[0].children[key].view if len(base.nodes) > 0 else _EmptyContainerView(child)
                              else:
                                  raise KeyError(f"Graph property '{base.prop.name}' does not have a child property '{key}'.")
                      self._view = _ChildrenView()
                  return self._view

              def __contains__(self, node: 'Node') -> bool:
                  return node in self.keys

              def __iter__(self) -> Iterator['Node']:
                  return iter(self.nodes)

              def append(self, node):
                  if node not in self.keys:
                      self.keys.add(node)
                      self.nodes.append(node)

          def __init__(self, prop: GraphTemplate.Property, entity: Any, key: Optional[Any], index: int):
              #: Template property.
              self.prop = prop
              #: An entity value.
              self.entity = entity
              self.key = key
              self.parents = set()
              self.children: dict[str, Node.Children] = {c.name: Node.Children(c) for c in prop.children}
              self._index = index
              self._view = None

          def __contains__(self, key: str) -> bool:
              return key in self.children

          @property
          def name(self) -> str:
              """
              Returns the container name, which is same as the name of template property.
              """
              return self.prop.name

          @property
          def view(self) -> NodeView:
              """
              Returns an unmodifiable view of this node.

              The view object works as the accessor to entity and child nodes.
              """
              if self._view is None:
                  node = self
                  class _NodeView(NodeView):
                      def __call__(self, alt: Any = None) -> Any:
                          """Returns an entity of this node."""
                          return node.entity
                      def __getattr__(self, key: str) -> ContainerView:
                          """Returns a view of child nodes by its name."""
                          return node.children[key].view
                      def __iter__(self) -> Iterator[tuple[str, ContainerView]]:
                          """Iterate key-value pairs of child nodes."""
                          return map(lambda nc: (nc[0], nc[1].view), node.children.items())
                  self._view = _NodeView()
              return self._view

          def add_child(self, child: 'Node') -> Self:
              """
              Adds a child node.

              Args:
                  child: Child node.
              Returns:
                  This instance.
              """
              if child.prop.template != self.prop.template:
                  raise ValueError(f"Nodes from different graph template can't be associated.")
              self.children[child.prop.name].append(child)
              child.parents.add(self)
              return self

          def has_child(self, child: 'Node') -> bool:
              """
              Checks this node contains the node identical to given node.

              Args:
                  child: Node to search.
              Returns:
                  `True` if exists.
              """
              if child.prop.template != self.prop.template:
                  return False
              elif child.prop.name in self.children:
                  return child in self.children[child.prop.name].keys
              else:
                  return False

  ### Subclasses

  - pyracmon.graph.graph._GraphNode

  ### Class variables

  `var Children`

    This class represents a child nodes of a node.

  ### Instance variables

  `var entity`

    An entity value.

  `var name : str`

    Returns the container name, which is same as the name of template property.

    Expand source code

        @property
        def name(self) -> str:
            """
            Returns the container name, which is same as the name of template property.
            """
            return self.prop.name

  `var prop`

    Template property.

  `var view : NodeView`

    Returns an unmodifiable view of this node.

    The view object works as the accessor to entity and child nodes.

    Expand source code

        @property
        def view(self) -> NodeView:
            """
            Returns an unmodifiable view of this node.

            The view object works as the accessor to entity and child nodes.
            """
            if self._view is None:
                node = self
                class _NodeView(NodeView):
                    def __call__(self, alt: Any = None) -> Any:
                        """Returns an entity of this node."""
                        return node.entity
                    def __getattr__(self, key: str) -> ContainerView:
                        """Returns a view of child nodes by its name."""
                        return node.children[key].view
                    def __iter__(self) -> Iterator[tuple[str, ContainerView]]:
                        """Iterate key-value pairs of child nodes."""
                        return map(lambda nc: (nc[0], nc[1].view), node.children.items())
                self._view = _NodeView()
            return self._view

  ### Methods

  `def add_child(self, child: Node) ‑> typing_extensions.Self`

    Adds a child node.

    ## Args

    `child`
      Child node.

    ## Returns

    This instance.

    Expand source code

        def add_child(self, child: 'Node') -> Self:
            """
            Adds a child node.

            Args:
                child: Child node.
            Returns:
                This instance.
            """
            if child.prop.template != self.prop.template:
                raise ValueError(f"Nodes from different graph template can't be associated.")
            self.children[child.prop.name].append(child)
            child.parents.add(self)
            return self

  `def has_child(self, child: Node) ‑> bool`

    Checks this node contains the node identical to given node.

    ## Args

    `child`
      Node to search.

    ## Returns

    `True` if exists.

    Expand source code

        def has_child(self, child: 'Node') -> bool:
            """
            Checks this node contains the node identical to given node.

            Args:
                child: Node to search.
            Returns:
                `True` if exists.
            """
            if child.prop.template != self.prop.template:
                return False
            elif child.prop.name in self.children:
                return child in self.children[child.prop.name].keys
            else:
                return False

`class NodeContainer (prop: GraphTemplate.Property)`

  This class represents a container of nodes for a template property.

  Expand source code

      class NodeContainer:
          """
          This class represents a container of nodes for a template property.
          """
          def __init__(self, prop: GraphTemplate.Property):
              #: Template property.
              self.prop = prop
              self.nodes: list[Node] = []
              self.keys: dict[Any, list[int]] = {}
              self._view = None

          @property
          def name(self) -> str:
              """
              Returns the container name, which is same as the name of template property.
              """
              return self.prop.name

          @property
          def view(self) -> ContainerView['NodeContainer']:
              """
              Returns an unmodifiable view of this container.

              The view object works as the accessor to container components.

              ```python
              template = GraphSpac().new_template(a=int, b=str, c=str)
              template.a << template.b
              graph = new_graph(template).append(a=1, b="a").append(a=1, b="b").append(a=2, b="c")
              container = graph.containers["a"]
              view = graph.view.a
              assert view() is container                             # invocation
              assert view.b is container.nodes[0].children["b"].view # attribute
              assert view[1] is container.nodes[1].view              # index
              assert [n() for n in view] == [1, 2]                   # iteration
              assert len(view) == 2                                  # length
              ```
              """
              if self._view is None:
                  container = self
                  class _ContainerView:
                      def __bool__(self):
                          """Returns whether this container is not empty."""
                          return len(container.nodes) != 0
                      def __call__(self) -> NodeContainer:
                          """Returns a base container."""
                          return container
                      def __len__(self):
                          """Returns the number of nodes."""
                          return len(container.nodes)
                      def __iter__(self):
                          """Iterates views of nodes."""
                          return map(lambda n: n.view, container.nodes)
                      @overload
                      def __getitem__(self, index: int) -> 'NodeView': ...
                      @overload
                      def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
                      def __getitem__(self, index: Union[int, slice]) -> Union['NodeView', Iterable['NodeView']]:
                          """Returns a view of a node at the index."""
                          if isinstance(index, slice):
                              return [n.view for n in container.nodes[index]]
                          else:
                              return container.nodes[index].view
                      def __getattr__(self, key) -> ContainerView:
                          """Returns a view of the first node or empty container view if it does not exist."""
                          child = next(filter(lambda c: c.name == key, container.prop.children), None)
                          if child:
                              return container.nodes[0].children[key].view if len(container.nodes) > 0 else _EmptyContainerView(child)
                          else:
                              raise KeyError(f"Graph property '{container.prop.name}' does not have a child property '{key}'.")
                  self._view = _ContainerView()
              return self._view

          def append(self, entity: Any, ancestors: MutableMapping[str, list['Node']], to_replace: bool = False):
              """
              Add an entity to this container.

              Identical node is searched by examining whether this container already contains a node of the identical entity
              and its parent is found in `anscestors` .

              Args:
                  entity: An entity to be stored in the node.
                  ancestors: Parent nodes mapped by property names.
                  to_replace: If `True`, the entity of identical node is replaced. Otherwise, it is not changed.
              """
              policy: IdentifyPolicy = self.prop.policy or neverPolicy()

              key = policy.get_identifier(entity)

              parents, identicals = policy.identify(self.prop, [self.nodes[i] for i in self.keys.get(key, [])], ancestors)

              new_nodes = identicals.copy()

              for pn in parents:
                  index = len(self.nodes)

                  node = Node(self.prop, entity, key, index)
                  self.nodes.append(node)
                  if key is not None:
                      self.keys.setdefault(key, []).append(index)
                  new_nodes.append(node)

                  if pn is not None:
                      pn.add_child(node)

              if to_replace:
                  for n in identicals:
                      n.entity = entity

              ancestors[self.prop.name] = new_nodes

  ### Subclasses

  - pyracmon.graph.graph._GraphNodeContainer

  ### Instance variables

  `var name : str`

    Returns the container name, which is same as the name of template property.

    Expand source code

        @property
        def name(self) -> str:
            """
            Returns the container name, which is same as the name of template property.
            """
            return self.prop.name

  `var prop`

    Template property.

  `var view : ContainerView[NodeContainer]`

    Returns an unmodifiable view of this container.

    The view object works as the accessor to container components.

        template = GraphSpac().new_template(a=int, b=str, c=str)
        template.a << template.b
        graph = new_graph(template).append(a=1, b="a").append(a=1, b="b").append(a=2, b="c")
        container = graph.containers["a"]
        view = graph.view.a
        assert view() is container                             # invocation
        assert view.b is container.nodes[0].children["b"].view # attribute
        assert view[1] is container.nodes[1].view              # index
        assert [n() for n in view] == [1, 2]                   # iteration
        assert len(view) == 2                                  # length

    Expand source code

        @property
        def view(self) -> ContainerView['NodeContainer']:
            """
            Returns an unmodifiable view of this container.

            The view object works as the accessor to container components.

            ```python
            template = GraphSpac().new_template(a=int, b=str, c=str)
            template.a << template.b
            graph = new_graph(template).append(a=1, b="a").append(a=1, b="b").append(a=2, b="c")
            container = graph.containers["a"]
            view = graph.view.a
            assert view() is container                             # invocation
            assert view.b is container.nodes[0].children["b"].view # attribute
            assert view[1] is container.nodes[1].view              # index
            assert [n() for n in view] == [1, 2]                   # iteration
            assert len(view) == 2                                  # length
            ```
            """
            if self._view is None:
                container = self
                class _ContainerView:
                    def __bool__(self):
                        """Returns whether this container is not empty."""
                        return len(container.nodes) != 0
                    def __call__(self) -> NodeContainer:
                        """Returns a base container."""
                        return container
                    def __len__(self):
                        """Returns the number of nodes."""
                        return len(container.nodes)
                    def __iter__(self):
                        """Iterates views of nodes."""
                        return map(lambda n: n.view, container.nodes)
                    @overload
                    def __getitem__(self, index: int) -> 'NodeView': ...
                    @overload
                    def __getitem__(self, index: slice) -> Iterable['NodeView']: ...
                    def __getitem__(self, index: Union[int, slice]) -> Union['NodeView', Iterable['NodeView']]:
                        """Returns a view of a node at the index."""
                        if isinstance(index, slice):
                            return [n.view for n in container.nodes[index]]
                        else:
                            return container.nodes[index].view
                    def __getattr__(self, key) -> ContainerView:
                        """Returns a view of the first node or empty container view if it does not exist."""
                        child = next(filter(lambda c: c.name == key, container.prop.children), None)
                        if child:
                            return container.nodes[0].children[key].view if len(container.nodes) > 0 else _EmptyContainerView(child)
                        else:
                            raise KeyError(f"Graph property '{container.prop.name}' does not have a child property '{key}'.")
                self._view = _ContainerView()
            return self._view

  ### Methods

  `def append(self, entity: Any, ancestors: collections.abc.MutableMapping[str, list['Node']], to_replace: bool = False)`

    Add an entity to this container.

    Identical node is searched by examining whether this container already contains a node of the identical entity and its parent is found in `anscestors` .

    ## Args

    `entity`
      An entity to be stored in the node.
    `ancestors`
      Parent nodes mapped by property names.
    `to_replace`
      If `True`, the entity of identical node is replaced. Otherwise, it is not changed.
    Expand source code

        def append(self, entity: Any, ancestors: MutableMapping[str, list['Node']], to_replace: bool = False):
            """
            Add an entity to this container.

            Identical node is searched by examining whether this container already contains a node of the identical entity
            and its parent is found in `anscestors` .

            Args:
                entity: An entity to be stored in the node.
                ancestors: Parent nodes mapped by property names.
                to_replace: If `True`, the entity of identical node is replaced. Otherwise, it is not changed.
            """
            policy: IdentifyPolicy = self.prop.policy or neverPolicy()

            key = policy.get_identifier(entity)

            parents, identicals = policy.identify(self.prop, [self.nodes[i] for i in self.keys.get(key, [])], ancestors)

            new_nodes = identicals.copy()

            for pn in parents:
                index = len(self.nodes)

                node = Node(self.prop, entity, key, index)
                self.nodes.append(node)
                if key is not None:
                    self.keys.setdefault(key, []).append(index)
                new_nodes.append(node)

                if pn is not None:
                    pn.add_child(node)

            if to_replace:
                for n in identicals:
                    n.entity = entity

            ancestors[self.prop.name] = new_nodes

`class NodeView`
  Expand source code

      class NodeView:
          def __call__(self, alt: Any = None) -> Any:
              """Returns an entity of this node."""
              ...
          def __getattr__(self, key: str) -> ContainerView:
              """Returns a view of child nodes by its name."""
              ...
          def __iter__(self) -> Iterator[tuple[str, ContainerView]]:
              """Iterate key-value pairs of child nodes."""
              ...