Skip to content

History and network states

History is implemented directly by Network; there is no separate NetworkHistory class. Mutating decorated methods save deep node/edge snapshots as NetworkState objects. Undo, redo, checkout, and subsequent mutations form a branching tree.

net.connect_nodes()
states = net.list_states()
net.undo()
net.redo()
html = net.history_html()

Network history methods

Network

Network(initial_nodes: list[str] = None, sif_file=None, resources=None)

A molecular interaction network.

The `Network` object is the central organizing component of the `neko`
module. It is the subject of all operations implemented here, including
topological algorithms, graph analysis, network visualization and
integration of database knowledge.

Args: initial_nodes: A list of initial nodes to be added to the network. sif_file: A SIF (Simple Interaction Format) file to load the network from. resources: A pandas DataFrame containing the resources database.

Methods:

Source code in neko/core/network.py
def __init__(
        self,
        initial_nodes: list[str] = None,
        sif_file=None,
        resources=None,
    ):

    self._init_args = locals()
    del self._init_args['self']
    self.nodes = pd.DataFrame(columns=["Genesymbol", "Uniprot", "Type"])
    self.edges = pd.DataFrame(columns=["source", "target", "Type", "Effect", "References"])
    # Internal object-based storage
    self._node_objs = set()  # Set of Node objects
    self._edge_objs = set()  # Set of Edge objects
    self.initial_nodes = initial_nodes
    self._ontology = Ontology()
    # --- NetworkState history tracking ---
    self._states: dict[int, NetworkState] = {}
    self._state_metadata: dict[int, dict] = {}
    self._state_log: list[int] = []
    self._state_counter: int = 0
    self._current_state_id: Optional[int] = None
    self._root_state_id: Optional[int] = None
    self._auto_state_depth: int = 0
    self._history_enabled: bool = True
    self._max_history: Optional[int] = None
    self._is_initializing = True
    self._populate()
    self._is_initializing = False

Attributes

current_state_id property
current_state_id: Optional[int]
root_state_id property
root_state_id: Optional[int]

Methods:

save_state
save_state(metadata: Optional[dict] = None) -> int

Persist the current network snapshot and return its state id.

Source code in neko/core/network.py
def save_state(self, metadata: Optional[dict] = None) -> int:
    """Persist the current network snapshot and return its state id."""

    parent_id = self._current_state_id
    state = self._build_state(metadata, parent_id)
    state_id = state.state_id
    self._states[state_id] = state
    self._state_metadata[state_id] = state.metadata
    self._state_log.append(state_id)

    if parent_id is None:
        self._root_state_id = state_id
    else:
        parent_state = self._states[parent_id]
        parent_state.add_child(state_id)

    self._current_state_id = state_id
    self._state_counter += 1
    self._enforce_max_history()
    return state_id
set_max_history
set_max_history(max_states: Optional[int]) -> None

Set the maximum number of stored history states (None disables pruning).

Source code in neko/core/network.py
def set_max_history(self, max_states: Optional[int]) -> None:
    """Set the maximum number of stored history states (None disables pruning)."""

    if max_states is None:
        self._max_history = None
    else:
        max_states = int(max_states)
        if max_states < 2:
            max_states = 2
        self._max_history = max_states
    self._enforce_max_history()
set_history_tracking
set_history_tracking(enabled: bool) -> None

Globally enable or disable automatic state capture.

Source code in neko/core/network.py
def set_history_tracking(self, enabled: bool) -> None:
    """Globally enable or disable automatic state capture."""

    self._history_enabled = bool(enabled)
suspend_history
suspend_history()

Temporarily suspend automatic state capture within the context.

Source code in neko/core/network.py
@contextmanager
def suspend_history(self):
    """Temporarily suspend automatic state capture within the context."""

    previous = self._history_enabled
    self._history_enabled = False
    try:
        yield
    finally:
        self._history_enabled = previous
list_states
list_states() -> list

Return a creation-ordered list of state metadata.

Source code in neko/core/network.py
def list_states(self) -> list:
    """Return a creation-ordered list of state metadata."""

    return [
        {"id": state_id, "metadata": self._state_metadata.get(state_id, {})}
        for state_id in self._state_log
    ]
checkout
checkout(state_id: int) -> None

Restore the network to a previously saved state.

Source code in neko/core/network.py
def checkout(self, state_id: int) -> None:
    """Restore the network to a previously saved state."""

    state = self._get_state(state_id)
    self.nodes = state.nodes.copy(deep=True)
    self.edges = state.edges.copy(deep=True)
    self._current_state_id = state_id
restore_state
restore_state(state_id: Optional[int] = None) -> None

Backward compatible wrapper around :meth:checkout.

Source code in neko/core/network.py
def restore_state(self, state_id: Optional[int] = None) -> None:
    """Backward compatible wrapper around :meth:`checkout`."""

    target = state_id if state_id is not None else self._current_state_id
    if target is None:
        raise ValueError("No saved states to restore.")
    self.checkout(self._resolve_state_id(target))
undo
undo() -> None

Move to the parent state if available.

Source code in neko/core/network.py
def undo(self) -> None:
    """Move to the parent state if available."""

    if self._current_state_id is None:
        return
    parents = self._get_state(self._current_state_id).parent_ids
    if not parents:
        return
    self.checkout(parents[-1])
redo
redo(state_id: Optional[int] = None) -> None

Move to a child state. If more than one child exists, a specific state id is required.

Source code in neko/core/network.py
def redo(self, state_id: Optional[int] = None) -> None:
    """Move to a child state. If more than one child exists, a specific state id is required."""

    if self._current_state_id is None:
        return
    children = self._get_state(self._current_state_id).children_ids
    if not children:
        return
    target = state_id
    if target is None:
        if len(children) != 1:
            raise ValueError("Multiple branches available; specify a target state id.")
        target = children[0]
    elif target not in children:
        raise ValueError(f"State {target} is not a child of {self._current_state_id}.")
    self.checkout(target)
compare_states
compare_states(state_a: int, state_b: int) -> dict

Compare two states identified by their ids.

Source code in neko/core/network.py
def compare_states(self, state_a: int, state_b: int) -> dict:
    """Compare two states identified by their ids."""

    resolved_a = self._resolve_state_id(state_a)
    resolved_b = self._resolve_state_id(state_b)
    state1 = self._get_state(resolved_a)
    state2 = self._get_state(resolved_b)
    nodes1 = self._state_node_labels(state1)
    nodes2 = self._state_node_labels(state2)
    edges1 = self._state_edge_signatures(state1)
    edges2 = self._state_edge_signatures(state2)
    diff = {
        "added_nodes": list(nodes2 - nodes1),
        "removed_nodes": list(nodes1 - nodes2),
        "added_edges": list(edges2 - edges1),
        "removed_edges": list(edges1 - edges2),
    }
    return diff
describe_history
describe_history() -> None

Pretty-print the branching history tree.

Source code in neko/core/network.py
def describe_history(self) -> None:
    """Pretty-print the branching history tree."""

    if not self._states:
        print("<no states recorded>")
        return

    def _label(state: NetworkState) -> str:
        meta = state.metadata or {}
        label = meta.get("label") or meta.get("description")
        if label:
            return str(label)
        if meta:
            return str(meta)
        return ""

    def _walk(state_id: int, depth: int) -> None:
        state = self._get_state(state_id)
        indent = "  " * depth
        label = _label(state)
        suffix = f" - {label}" if label else ""
        print(f"{indent}State {state_id}{suffix}")
        for child_id in state.children_ids:
            _walk(child_id, depth + 1)

    _walk(self._root_state_id, 0)
describe_states
describe_states() -> None

Backward compatible alias for :meth:describe_history.

Source code in neko/core/network.py
def describe_states(self) -> None:
    """Backward compatible alias for :meth:`describe_history`."""

    self.describe_history()
history_graph
history_graph()

Return a networkx DiGraph representing the state transitions.

Source code in neko/core/network.py
def history_graph(self):
    """Return a networkx DiGraph representing the state transitions."""

    from .._visual.history import build_history_graph

    return build_history_graph(self)
history_digraph
history_digraph(include_metadata: bool = True)

Return a Graphviz digraph for the history.

Source code in neko/core/network.py
def history_digraph(self, include_metadata: bool = True):
    """Return a Graphviz digraph for the history."""

    from .._visual.history import history_digraph

    return history_digraph(self, include_metadata=include_metadata)
history_html
history_html(include_metadata: bool = True, div_class: str = 'neko-history-graph') -> str

Return an HTML snippet embedding the history graph.

Source code in neko/core/network.py
def history_html(self, include_metadata: bool = True, div_class: str = "neko-history-graph") -> str:
    """Return an HTML snippet embedding the history graph."""

    from .._visual.history import history_html

    return history_html(self, include_metadata=include_metadata, div_class=div_class)

Snapshot value object

NetworkState

NetworkState(nodes: DataFrame, edges: DataFrame, metadata: Optional[Dict[str, Any]] = None, state_id: Optional[int] = None, parent_ids: Optional[List[int]] = None)

Immutable snapshot of a network, with parent/child bookkeeping.

Source code in neko/core/network_state.py
def __init__(
    self,
    nodes: pd.DataFrame,
    edges: pd.DataFrame,
    metadata: Optional[Dict[str, Any]] = None,
    state_id: Optional[int] = None,
    parent_ids: Optional[List[int]] = None,
):
    self.nodes = nodes.copy(deep=True)
    self.edges = edges.copy(deep=True)
    self.metadata: Dict[str, Any] = metadata or {}
    self.state_id = state_id
    self.parent_ids: List[int] = list(parent_ids or [])
    self.children_ids: List[int] = []

Rendering helpers

build_history_graph

build_history_graph(network: 'Network') -> nx.DiGraph

Return a directed graph of the network state history.

Source code in neko/_visual/history.py
def build_history_graph(network: "Network") -> nx.DiGraph:
    """Return a directed graph of the network state history."""

    graph = nx.DiGraph()
    for state_id in network.list_states():
        # list_states returns dictionaries with id/metadata
        graph.add_node(state_id["id"], **state_id.get("metadata", {}))
    for state_id, state in network._states.items():  # pylint: disable=protected-access
        for child in state.children_ids:
            graph.add_edge(state_id, child)
    return graph

history_digraph

history_digraph(network: 'Network', include_metadata: bool = True) -> Digraph

Render history as a Graphviz Digraph.

Source code in neko/_visual/history.py
def history_digraph(network: "Network", include_metadata: bool = True) -> Digraph:
    """Render history as a Graphviz Digraph."""

    digraph = Digraph()

    for state_id, state in network._states.items():  # pylint: disable=protected-access
        metadata = state.metadata or {}
        label_lines = [f"State {state_id}"]
        if metadata.get("label"):
            label_lines.append(str(metadata["label"]))
        if include_metadata and metadata.get("method"):
            args = metadata.get("args", [])
            kwargs = metadata.get("kwargs", {})
            params = ", ".join(args)
            if kwargs:
                kwargs_repr = ", ".join(f"{k}={v}" for k, v in kwargs.items())
                params = ", ".join(filter(None, [params, kwargs_repr]))
            label_lines.append(f"{metadata['method']}({params})")
        elif include_metadata:
            extras = {
                key: value for key, value in metadata.items()
                if key not in {"label", "method", "args", "kwargs"}
            }
            for key, value in extras.items():
                label_lines.append(f"{key}: {value}")
        digraph.node(str(state_id), "\n".join(label_lines))
        for child in state.children_ids:
            digraph.edge(str(state_id), str(child))
    return digraph

history_html

history_html(network: 'Network', include_metadata: bool = True, div_class: str = 'neko-history-graph') -> str

Return an HTML snippet embedding the history graph as SVG.

Source code in neko/_visual/history.py
def history_html(network: "Network", include_metadata: bool = True, div_class: str = "neko-history-graph") -> str:
    """Return an HTML snippet embedding the history graph as SVG."""

    svg = history_digraph(network, include_metadata=include_metadata).pipe(format="svg").decode("utf-8")
    return f'<div class="{div_class}">{svg}</div>'