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 8 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["automatic_backends"]:
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 @@ -23,8 +23,8 @@ def test_pickle():


@pytest.mark.skipif(
"not nx._dispatchable._automatic_backends "
"or nx._dispatchable._automatic_backends[0] != 'nx-loopback'"
"not nx.config['automatic_backends'] "
"or nx.config['automatic_backends'][0] != 'nx-loopback'"
)
def test_graph_converter_needs_backend():
# When testing, `nx.from_scipy_sparse_array` will *always* call the backend
Expand Down
4 changes: 2 additions & 2 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["automatic_backends"] = [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,7 +70,7 @@ 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 automatic_backends := networkx.config["automatic_backends"]:
# 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()
Expand Down
32 changes: 21 additions & 11 deletions networkx/utils/backends.py
Expand Up @@ -95,7 +95,7 @@ class WrappedSparse:

from ..exception import NetworkXNotImplemented

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


def _get_backends(group, *, load_and_call=False):
Expand Down Expand Up @@ -127,6 +127,20 @@ 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
"automatic_backends": [
x.strip()
for x in 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()
},
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be for a separate PR but I think the config entry "automatic backends" could be named better (same for the corresponding env var), especially when seen next to the config entry here for "backends".

We should discuss this a bit more, but I think "backend priority" (config["backend_priority"], NETWORKX_BACKEND_PRIORITY) would be a better name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good thing to discuss 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think maybe we can think a bit more about the schema for the config dictionary, but for now let's go with this!


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

Expand Down Expand Up @@ -157,11 +171,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 @@ -436,10 +445,11 @@ def __call__(self, /, *args, backend=None, **kwargs):
# if (val := args[pos] if pos < len(args) else kwargs.get(gname)) is not None
# }

if self._is_testing and self._automatic_backends and backend_name is None:
automatic_backends = config["automatic_backends"]
if self._is_testing and automatic_backends and backend_name is None:
# Special path if we are running networkx tests with a backend.
return self._convert_and_call_for_tests(
self._automatic_backends[0],
automatic_backends[0],
args,
kwargs,
fallback_to_nx=self._fallback_to_nx,
Expand Down Expand Up @@ -504,7 +514,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 automatic_backends
):
# Not configured to convert networkx graphs to this backend
raise TypeError(
Expand All @@ -524,7 +534,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 automatic_backends
raise NetworkXNotImplemented(
f"'{self.name}' not implemented by {graph_backend_name}"
)
Expand All @@ -538,7 +548,7 @@ def __call__(self, /, *args, backend=None, **kwargs):
# Only networkx graphs; try to convert and run with a backend with automatic
# conversion, but don't do this by default for graph generators or loaders.
if self.graphs:
for backend_name in self._automatic_backends:
for backend_name in automatic_backends:
if self._can_backend_run(backend_name, *args, **kwargs):
return self._convert_and_call(
backend_name,
Expand Down