Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nx.config dict for configuring dispatching and backends #7225

Merged
merged 20 commits into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion networkx/__init__.py
Expand Up @@ -17,7 +17,7 @@
from networkx.exception import *

from networkx import utils
from networkx.utils.backends import _dispatchable
from networkx.utils.backends import _dispatchable, config

from networkx import classes
from networkx.classes import filters
Expand Down
2 changes: 1 addition & 1 deletion networkx/algorithms/operators/tests/test_binary.py
Expand Up @@ -53,7 +53,7 @@ def test_intersection():
assert set(I2.nodes()) == {1, 2, 3, 4}
assert sorted(I2.edges()) == [(2, 3)]
# Only test if not performing auto convert testing of backend implementations
if not nx.utils.backends._dispatchable._automatic_backends:
if not nx.config["backend_priority"]:
with pytest.raises(TypeError):
nx.intersection(G2, H)
with pytest.raises(TypeError):
Expand Down
4 changes: 2 additions & 2 deletions networkx/classes/tests/test_backends.py
Expand Up @@ -31,8 +31,8 @@ def test_pickle():


@pytest.mark.skipif(
"not nx._dispatchable._automatic_backends "
"or nx._dispatchable._automatic_backends[0] != 'nx-loopback'"
"not nx.config['backend_priority'] "
"or nx.config['backend_priority'][0] != 'nx-loopback'"
)
def test_graph_converter_needs_backend():
# When testing, `nx.from_scipy_sparse_array` will *always* call the backend
Expand Down
6 changes: 3 additions & 3 deletions networkx/conftest.py
Expand Up @@ -46,7 +46,7 @@ def pytest_configure(config):
if backend is None:
backend = os.environ.get("NETWORKX_TEST_BACKEND")
if backend:
networkx.utils.backends._dispatchable._automatic_backends = [backend]
networkx.config["backend_priority"] = [backend]
fallback_to_nx = config.getoption("--fallback-to-nx")
if not fallback_to_nx:
fallback_to_nx = os.environ.get("NETWORKX_FALLBACK_TO_NX")
Expand All @@ -70,10 +70,10 @@ def pytest_collection_modifyitems(config, items):
# Setting this to True here allows tests to be set up before dispatching
# any function call to a backend.
networkx.utils.backends._dispatchable._is_testing = True
if automatic_backends := networkx.utils.backends._dispatchable._automatic_backends:
if backend_priority := networkx.config["backend_priority"]:
# Allow pluggable backends to add markers to tests (such as skip or xfail)
# when running in auto-conversion test mode
backend = networkx.utils.backends.backends[automatic_backends[0]].load()
backend = networkx.utils.backends.backends[backend_priority[0]].load()
if hasattr(backend, "on_start_tests"):
getattr(backend, "on_start_tests")(items)

Expand Down
35 changes: 24 additions & 11 deletions networkx/utils/backends.py
Expand Up @@ -106,7 +106,7 @@ class WrappedSparse:
from ..exception import NetworkXNotImplemented
from .decorators import argmap

__all__ = ["_dispatchable"]
__all__ = ["_dispatchable", "config"]


def _do_nothing():
Expand Down Expand Up @@ -142,6 +142,23 @@ def _get_backends(group, *, load_and_call=False):
backends = _get_backends("networkx.backends")
backend_info = _get_backends("networkx.backend_info", load_and_call=True)

config = {
# Get default configuration from environment variables at import time
"backend_priority": [
x.strip()
for x in os.environ.get(
"NETWORKX_BACKEND_PRIORITY",
os.environ.get("NETWORKX_AUTOMATIC_BACKENDS", ""),
).split(",")
if x.strip()
],
# Initialize default configuration for backends
"backends": {
backend: info.get("default_config", {})
for backend, info in backend_info.items()
},
}

# Load and cache backends on-demand
_loaded_backends = {} # type: ignore[var-annotated]

Expand Down Expand Up @@ -180,11 +197,6 @@ class _dispatchable:
_fallback_to_nx = (
os.environ.get("NETWORKX_FALLBACK_TO_NX", "true").strip().lower() == "true"
)
_automatic_backends = [
x.strip()
for x in os.environ.get("NETWORKX_AUTOMATIC_BACKENDS", "").split(",")
if x.strip()
]

def __new__(
cls,
Expand Down Expand Up @@ -532,11 +544,12 @@ def __call__(self, /, *args, backend=None, **kwargs):
for g in graphs_resolved.values()
}

if self._is_testing and self._automatic_backends and backend_name is None:
backend_priority = config["backend_priority"]
if self._is_testing and backend_priority and backend_name is None:
# Special path if we are running networkx tests with a backend.
# This even runs for (and handles) functions that mutate input graphs.
return self._convert_and_call_for_tests(
self._automatic_backends[0],
backend_priority[0],
args,
kwargs,
fallback_to_nx=self._fallback_to_nx,
Expand All @@ -563,7 +576,7 @@ def __call__(self, /, *args, backend=None, **kwargs):
raise ImportError(f"Unable to load backend: {graph_backend_name}")
if (
"networkx" in graph_backend_names
and graph_backend_name not in self._automatic_backends
and graph_backend_name not in backend_priority
):
# Not configured to convert networkx graphs to this backend
raise TypeError(
Expand All @@ -584,7 +597,7 @@ def __call__(self, /, *args, backend=None, **kwargs):
)
# All graphs are backend graphs--no need to convert!
return getattr(backend, self.name)(*args, **kwargs)
# Future work: try to convert and run with other backends in self._automatic_backends
# Future work: try to convert and run with other backends in backend_priority
raise NetworkXNotImplemented(
f"'{self.name}' not implemented by {graph_backend_name}"
)
Expand Down Expand Up @@ -622,7 +635,7 @@ def __call__(self, /, *args, backend=None, **kwargs):
)
):
# Should we warn or log if we don't convert b/c the input will be mutated?
for backend_name in self._automatic_backends:
for backend_name in backend_priority:
if self._should_backend_run(backend_name, *args, **kwargs):
return self._convert_and_call(
backend_name,
Expand Down