Source code for turbograph.backend.factory
"""Factory module for selecting and managing graph backends.
This module provides functions to:
- Check the availability of graph backends (``networkx`` and ``igraph``).
- Automatically determine the best available backend.
- Retrieve the appropriate backend wrapper class for graph operations.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, NoReturn
if TYPE_CHECKING: # pragma: no cover
from .igraph_backend import IGraphWrapper
from .networkx_backend import NetworkXWrapper
Backend = Literal["networkx", "igraph"]
"""Enumeration of supported graph backends."""
BACKENDS: tuple[Backend, ...] = ("networkx", "igraph")
"""Tuple of available graph backends.
The first available backend is selected as the default unless explicitly specified.
"""
backend_to_module: dict[Backend, str] = {
"igraph": "igraph",
"networkx": "networkx",
}
"""Mapping of backend names to their corresponding module names.
This mapping is used to dynamically check the availability of backend libraries.
"""
def _raise_invalid_backend(
backend: Backend, origin: Exception | None = None
) -> NoReturn:
"""Raise an error for an invalid backend name.
Args:
backend: The name of the backend.
origin: The original exception that triggered the error (if any).
Raises:
ValueError: Always.
"""
msg = f"Backend {backend!r} is invalid. Valid backends are: " + ", ".join(
backend_to_module.keys()
)
raise ValueError(msg) from origin
def _is_available(backend: Backend) -> bool:
r"""Check if the specified backend is installed and available for use.
Args:
backend: The name of the backend (``"igraph"`` or ``"networkx"``).
Returns:
True if the backend's Python package is installed, False otherwise.
Raises:
ValueError: If an unrecognized backend name is provided.
Example:
>>> _is_available("igraph")
True # If 'igraph' is installed
"""
import importlib.util
try:
module_name = backend_to_module[backend]
except KeyError as error:
_raise_invalid_backend(backend, error)
return importlib.util.find_spec(module_name) is not None
[docs]
def get_backend_auto() -> Backend:
"""Automatically select the best available graph backend.
Returns:
The first available backend from the predefined list (:py:data:`BACKENDS`).
Raises:
ImportError: If no supported backend is installed.
Example:
>>> get_backend_auto()
'networkx' # If 'networkx' is installed
"""
for backend in BACKENDS:
if _is_available(backend):
return backend
msg = (
"No available graph backend. "
"Please install either 'networkx' or 'igraph' library."
)
raise ImportError(msg)
[docs]
def get_graph_backend(
backend: Literal["igraph", "networkx"] | None = None,
) -> type[NetworkXWrapper[Any] | IGraphWrapper[Any]]:
"""Retrieve the appropriate backend wrapper class for graph operations.
Args:
backend: The backend to use. If `None`, the best available backend is
selected automatically using :py:func:`get_backend_auto`.
Returns:
The backend wrapper class.
Raises:
ValueError: If an unrecognized backend name is provided.
ImportError: If no supported backend is installed.
"""
if backend is None:
backend = get_backend_auto()
if backend == "igraph":
from .igraph_backend import IGraphWrapper
return IGraphWrapper
elif backend == "networkx":
from .networkx_backend import NetworkXWrapper
return NetworkXWrapper
else:
_raise_invalid_backend(backend)