Source code for flpq_data.graphs.converters

"""Format conversion over edge streams."""

import logging
import pathlib
from typing import Any, Callable, Dict, Optional, Union

import networkx as nx

from flpq_data.graphs.readwrite.graph import iter_edges_from_graph
from flpq_data.graphs.readwrite.mtx import (
    iter_edges_from_mtx_dir,
    mtx_dir_from_edges,
)
from flpq_data.graphs.readwrite.rdf import iter_edges_from_rdf, rdf_from_edges
from flpq_data.graphs.readwrite.txt import iter_edges_from_txt, txt_from_edges
from flpq_data.graphs.utils.to_g_text import g_text_from_edges

__all__ = ["convert_graph"]

#: The conversion formats: the reader (source) and writer (destination) of
#: each; a ``None`` side means the direction is unsupported. See the "Format
#: conversion" section of ``docs/graphs/index.rst``.
_FORMATS: Dict[str, Dict[str, Optional[Callable[..., Any]]]] = {
    "mtx": {"read": iter_edges_from_mtx_dir, "write": mtx_dir_from_edges},
    "txt": {"read": iter_edges_from_txt, "write": txt_from_edges},
    "rdf": {"read": iter_edges_from_rdf, "write": rdf_from_edges},
    "g": {"read": None, "write": g_text_from_edges},
    "graph": {"read": iter_edges_from_graph, "write": None},
}


[docs] def convert_graph( src: Union[pathlib.Path, str, nx.MultiDiGraph], dst: Union[pathlib.Path, str], *, src_format: str, dst_format: str, **writer_options: Any, ) -> pathlib.Path: """Converts a graph between formats over an edge stream. The conversion runs over a lazy ``(u, label, v)`` edge stream — one streaming reader and one streaming writer per format — without building an in-memory ``networkx.MultiDiGraph`` (the "Format conversion" section of ``docs/graphs/index.rst``). Formats are explicit strings; the file extension is not inspected (an MTX directory is ambiguous with a single ``results.mtx`` file). Parameters ---------- src : Union[Path, str, MultiDiGraph] The source: a path for the file formats (``mtx``, ``txt``, ``rdf``) or an in-memory graph for ``graph``. dst : Union[Path, str] The destination path (a directory for ``mtx``, a file otherwise). src_format : str The format of ``src``: one of ``mtx``, ``txt``, ``rdf``, ``graph``. dst_format : str The format of ``dst``: one of ``mtx``, ``txt``, ``rdf``, ``g``. **writer_options : Any Forwarded to the destination writer (e.g. ``quoting=True`` for ``txt``, ``dimension=...`` for ``mtx``). Examples -------- >>> import pathlib, tempfile >>> d = pathlib.Path(tempfile.mkdtemp()) / "graph" >>> _ = d.mkdir(parents=True) >>> _ = (d / "a.mtx").write_text( ... "%%MatrixMarket matrix coordinate pattern general\\n" ... "%%GraphBLAS type bool\\n3 3 2\\n0 1\\n1 2\\n" ... ) >>> out = pathlib.Path(tempfile.mkdtemp()) / "g.txt" >>> _ = convert_graph(d, out, src_format="mtx", dst_format="txt") >>> out.read_text() '0 a 1\\n1 a 2\\n' Returns ------- path : Path The resolved destination path. Raises ------ ValueError If a format is unknown or the direction is unsupported (``g`` is write-only, ``graph`` is read-only). """ if src_format not in _FORMATS: raise ValueError( f"Unknown src_format {src_format!r}; " f"supported formats: {', '.join(sorted(_FORMATS))}" ) if dst_format not in _FORMATS: raise ValueError( f"Unknown dst_format {dst_format!r}; " f"supported formats: {', '.join(sorted(_FORMATS))}" ) read = _FORMATS[src_format]["read"] write = _FORMATS[dst_format]["write"] if read is None: readable = ", ".join( sorted(f for f in _FORMATS if _FORMATS[f]["read"] is not None) ) raise ValueError( f"{src_format!r} is not a readable format (readable: {readable})" ) if write is None: writable = ", ".join( sorted(f for f in _FORMATS if _FORMATS[f]["write"] is not None) ) raise ValueError( f"{dst_format!r} is not a writable format (writable: {writable})" ) dest = write(read(src), dst, **writer_options) logging.info(f"Convert {src=} from {src_format=} to {dst_format=}") return dest