263 lines
8.5 KiB
Python
263 lines
8.5 KiB
Python
import collections
|
|
import pickle
|
|
|
|
import pytest
|
|
|
|
import networkx as nx
|
|
from networkx.utils.configs import BackendPriorities, Config
|
|
|
|
|
|
# Define this at module level so we can test pickling
|
|
class ExampleConfig(Config):
|
|
"""Example configuration."""
|
|
|
|
x: int
|
|
y: str
|
|
|
|
def _on_setattr(self, key, value):
|
|
if key == "x" and value <= 0:
|
|
raise ValueError("x must be positive")
|
|
if key == "y" and not isinstance(value, str):
|
|
raise TypeError("y must be a str")
|
|
return value
|
|
|
|
|
|
class EmptyConfig(Config):
|
|
pass
|
|
|
|
|
|
@pytest.mark.parametrize("cfg", [EmptyConfig(), Config()])
|
|
def test_config_empty(cfg):
|
|
assert dir(cfg) == []
|
|
with pytest.raises(AttributeError):
|
|
cfg.x = 1
|
|
with pytest.raises(KeyError):
|
|
cfg["x"] = 1
|
|
with pytest.raises(AttributeError):
|
|
cfg.x
|
|
with pytest.raises(KeyError):
|
|
cfg["x"]
|
|
assert len(cfg) == 0
|
|
assert "x" not in cfg
|
|
assert cfg == cfg
|
|
assert cfg.get("x", 2) == 2
|
|
assert set(cfg.keys()) == set()
|
|
assert set(cfg.values()) == set()
|
|
assert set(cfg.items()) == set()
|
|
cfg2 = pickle.loads(pickle.dumps(cfg))
|
|
assert cfg == cfg2
|
|
assert isinstance(cfg, collections.abc.Collection)
|
|
assert isinstance(cfg, collections.abc.Mapping)
|
|
|
|
|
|
def test_config_subclass():
|
|
with pytest.raises(TypeError, match="missing 2 required keyword-only"):
|
|
ExampleConfig()
|
|
with pytest.raises(ValueError, match="x must be positive"):
|
|
ExampleConfig(x=0, y="foo")
|
|
with pytest.raises(TypeError, match="unexpected keyword"):
|
|
ExampleConfig(x=1, y="foo", z="bad config")
|
|
with pytest.raises(TypeError, match="unexpected keyword"):
|
|
EmptyConfig(z="bad config")
|
|
cfg = ExampleConfig(x=1, y="foo")
|
|
assert cfg.x == 1
|
|
assert cfg["x"] == 1
|
|
assert cfg["y"] == "foo"
|
|
assert cfg.y == "foo"
|
|
assert "x" in cfg
|
|
assert "y" in cfg
|
|
assert "z" not in cfg
|
|
assert len(cfg) == 2
|
|
assert set(iter(cfg)) == {"x", "y"}
|
|
assert set(cfg.keys()) == {"x", "y"}
|
|
assert set(cfg.values()) == {1, "foo"}
|
|
assert set(cfg.items()) == {("x", 1), ("y", "foo")}
|
|
assert dir(cfg) == ["x", "y"]
|
|
cfg.x = 2
|
|
cfg["y"] = "bar"
|
|
assert cfg["x"] == 2
|
|
assert cfg.y == "bar"
|
|
with pytest.raises(TypeError, match="can't be deleted"):
|
|
del cfg.x
|
|
with pytest.raises(TypeError, match="can't be deleted"):
|
|
del cfg["y"]
|
|
assert cfg.x == 2
|
|
assert cfg == cfg
|
|
assert cfg == ExampleConfig(x=2, y="bar")
|
|
assert cfg != ExampleConfig(x=3, y="baz")
|
|
assert cfg != Config(x=2, y="bar")
|
|
with pytest.raises(TypeError, match="y must be a str"):
|
|
cfg["y"] = 5
|
|
with pytest.raises(ValueError, match="x must be positive"):
|
|
cfg.x = -5
|
|
assert cfg.get("x", 10) == 2
|
|
with pytest.raises(AttributeError):
|
|
cfg.z = 5
|
|
with pytest.raises(KeyError):
|
|
cfg["z"] = 5
|
|
with pytest.raises(AttributeError):
|
|
cfg.z
|
|
with pytest.raises(KeyError):
|
|
cfg["z"]
|
|
cfg2 = pickle.loads(pickle.dumps(cfg))
|
|
assert cfg == cfg2
|
|
assert cfg.__doc__ == "Example configuration."
|
|
assert cfg2.__doc__ == "Example configuration."
|
|
|
|
|
|
def test_config_defaults():
|
|
class DefaultConfig(Config):
|
|
x: int = 0
|
|
y: int
|
|
|
|
cfg = DefaultConfig(y=1)
|
|
assert cfg.x == 0
|
|
cfg = DefaultConfig(x=2, y=1)
|
|
assert cfg.x == 2
|
|
|
|
|
|
def test_nxconfig():
|
|
assert isinstance(nx.config.backend_priority, BackendPriorities)
|
|
assert isinstance(nx.config.backend_priority.algos, list)
|
|
assert isinstance(nx.config.backends, Config)
|
|
with pytest.raises(TypeError, match="must be a list of backend names"):
|
|
nx.config.backend_priority.algos = "nx_loopback"
|
|
with pytest.raises(ValueError, match="Unknown backend when setting"):
|
|
nx.config.backend_priority.algos = ["this_almost_certainly_is_not_a_backend"]
|
|
with pytest.raises(TypeError, match="must be a Config of backend configs"):
|
|
nx.config.backends = {}
|
|
with pytest.raises(TypeError, match="must be a Config of backend configs"):
|
|
nx.config.backends = Config(plausible_backend_name={})
|
|
with pytest.raises(ValueError, match="Unknown backend when setting"):
|
|
nx.config.backends = Config(this_almost_certainly_is_not_a_backend=Config())
|
|
with pytest.raises(TypeError, match="must be True or False"):
|
|
nx.config.cache_converted_graphs = "bad value"
|
|
with pytest.raises(TypeError, match="must be a set of "):
|
|
nx.config.warnings_to_ignore = 7
|
|
with pytest.raises(ValueError, match="Unknown warning "):
|
|
nx.config.warnings_to_ignore = {"bad value"}
|
|
|
|
prev = nx.config.backend_priority
|
|
try:
|
|
nx.config.backend_priority = ["networkx"]
|
|
assert isinstance(nx.config.backend_priority, BackendPriorities)
|
|
assert nx.config.backend_priority.algos == ["networkx"]
|
|
finally:
|
|
nx.config.backend_priority = prev
|
|
|
|
|
|
def test_nxconfig_context():
|
|
# We do some special handling so that `nx.config.backend_priority = val`
|
|
# actually does `nx.config.backend_priority.algos = val`.
|
|
orig = nx.config.backend_priority.algos
|
|
val = [] if orig else ["networkx"]
|
|
assert orig != val
|
|
assert nx.config.backend_priority.algos != val
|
|
with nx.config(backend_priority=val):
|
|
assert nx.config.backend_priority.algos == val
|
|
assert nx.config.backend_priority.algos == orig
|
|
with nx.config.backend_priority(algos=val):
|
|
assert nx.config.backend_priority.algos == val
|
|
assert nx.config.backend_priority.algos == orig
|
|
bad = ["bad-backend"]
|
|
with pytest.raises(ValueError, match="Unknown backend"):
|
|
nx.config.backend_priority = bad
|
|
with pytest.raises(ValueError, match="Unknown backend"):
|
|
with nx.config(backend_priority=bad):
|
|
pass
|
|
with pytest.raises(ValueError, match="Unknown backend"):
|
|
with nx.config.backend_priority(algos=bad):
|
|
pass
|
|
|
|
|
|
def test_not_strict():
|
|
class FlexibleConfig(Config, strict=False):
|
|
x: int
|
|
|
|
cfg = FlexibleConfig(x=1)
|
|
assert "_strict" not in cfg
|
|
assert len(cfg) == 1
|
|
assert list(cfg) == ["x"]
|
|
assert list(cfg.keys()) == ["x"]
|
|
assert list(cfg.values()) == [1]
|
|
assert list(cfg.items()) == [("x", 1)]
|
|
assert cfg.x == 1
|
|
assert cfg["x"] == 1
|
|
assert "x" in cfg
|
|
assert hasattr(cfg, "x")
|
|
assert "FlexibleConfig(x=1)" in repr(cfg)
|
|
assert cfg == FlexibleConfig(x=1)
|
|
del cfg.x
|
|
assert "FlexibleConfig()" in repr(cfg)
|
|
assert len(cfg) == 0
|
|
assert not hasattr(cfg, "x")
|
|
assert "x" not in cfg
|
|
assert not hasattr(cfg, "y")
|
|
assert "y" not in cfg
|
|
cfg.y = 2
|
|
assert len(cfg) == 1
|
|
assert list(cfg) == ["y"]
|
|
assert list(cfg.keys()) == ["y"]
|
|
assert list(cfg.values()) == [2]
|
|
assert list(cfg.items()) == [("y", 2)]
|
|
assert cfg.y == 2
|
|
assert cfg["y"] == 2
|
|
assert hasattr(cfg, "y")
|
|
assert "y" in cfg
|
|
del cfg["y"]
|
|
assert len(cfg) == 0
|
|
assert list(cfg) == []
|
|
with pytest.raises(AttributeError, match="y"):
|
|
del cfg.y
|
|
with pytest.raises(KeyError, match="y"):
|
|
del cfg["y"]
|
|
with pytest.raises(TypeError, match="missing 1 required keyword-only"):
|
|
FlexibleConfig()
|
|
# Be strict when first creating the config object
|
|
with pytest.raises(TypeError, match="unexpected keyword argument 'y'"):
|
|
FlexibleConfig(x=1, y=2)
|
|
|
|
class FlexibleConfigWithDefault(Config, strict=False):
|
|
x: int = 0
|
|
|
|
assert FlexibleConfigWithDefault().x == 0
|
|
assert FlexibleConfigWithDefault(x=1)["x"] == 1
|
|
|
|
|
|
def test_context():
|
|
cfg = Config(x=1)
|
|
with cfg(x=2) as c:
|
|
assert c.x == 2
|
|
c.x = 3
|
|
assert cfg.x == 3
|
|
assert cfg.x == 1
|
|
|
|
with cfg(x=2) as c:
|
|
assert c == cfg
|
|
assert cfg.x == 2
|
|
with cfg(x=3) as c2:
|
|
assert c2 == cfg
|
|
assert cfg.x == 3
|
|
with pytest.raises(RuntimeError, match="context manager without"):
|
|
with cfg as c3: # Forgot to call `cfg(...)`
|
|
pass
|
|
assert cfg.x == 3
|
|
assert cfg.x == 2
|
|
assert cfg.x == 1
|
|
|
|
c = cfg(x=4) # Not yet as context (not recommended, but possible)
|
|
assert c == cfg
|
|
assert cfg.x == 4
|
|
# Cheat by looking at internal data; context stack should only grow with __enter__
|
|
assert cfg._prev is not None
|
|
assert cfg._context_stack == []
|
|
with c:
|
|
assert c == cfg
|
|
assert cfg.x == 4
|
|
assert cfg.x == 1
|
|
# Cheat again; there was no preceding `cfg(...)` call this time
|
|
assert cfg._prev is None
|
|
with pytest.raises(RuntimeError, match="context manager without"):
|
|
with cfg:
|
|
pass
|
|
assert cfg.x == 1
|