test_config.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import collections
  2. import pickle
  3. import pytest
  4. import networkx as nx
  5. from networkx.utils.configs import BackendPriorities, Config
  6. # Define this at module level so we can test pickling
  7. class ExampleConfig(Config):
  8. """Example configuration."""
  9. x: int
  10. y: str
  11. def _on_setattr(self, key, value):
  12. if key == "x" and value <= 0:
  13. raise ValueError("x must be positive")
  14. if key == "y" and not isinstance(value, str):
  15. raise TypeError("y must be a str")
  16. return value
  17. class EmptyConfig(Config):
  18. pass
  19. @pytest.mark.parametrize("cfg", [EmptyConfig(), Config()])
  20. def test_config_empty(cfg):
  21. assert dir(cfg) == []
  22. with pytest.raises(AttributeError):
  23. cfg.x = 1
  24. with pytest.raises(KeyError):
  25. cfg["x"] = 1
  26. with pytest.raises(AttributeError):
  27. cfg.x
  28. with pytest.raises(KeyError):
  29. cfg["x"]
  30. assert len(cfg) == 0
  31. assert "x" not in cfg
  32. assert cfg == cfg
  33. assert cfg.get("x", 2) == 2
  34. assert set(cfg.keys()) == set()
  35. assert set(cfg.values()) == set()
  36. assert set(cfg.items()) == set()
  37. cfg2 = pickle.loads(pickle.dumps(cfg))
  38. assert cfg == cfg2
  39. assert isinstance(cfg, collections.abc.Collection)
  40. assert isinstance(cfg, collections.abc.Mapping)
  41. def test_config_subclass():
  42. with pytest.raises(TypeError, match="missing 2 required keyword-only"):
  43. ExampleConfig()
  44. with pytest.raises(ValueError, match="x must be positive"):
  45. ExampleConfig(x=0, y="foo")
  46. with pytest.raises(TypeError, match="unexpected keyword"):
  47. ExampleConfig(x=1, y="foo", z="bad config")
  48. with pytest.raises(TypeError, match="unexpected keyword"):
  49. EmptyConfig(z="bad config")
  50. cfg = ExampleConfig(x=1, y="foo")
  51. assert cfg.x == 1
  52. assert cfg["x"] == 1
  53. assert cfg["y"] == "foo"
  54. assert cfg.y == "foo"
  55. assert "x" in cfg
  56. assert "y" in cfg
  57. assert "z" not in cfg
  58. assert len(cfg) == 2
  59. assert set(iter(cfg)) == {"x", "y"}
  60. assert set(cfg.keys()) == {"x", "y"}
  61. assert set(cfg.values()) == {1, "foo"}
  62. assert set(cfg.items()) == {("x", 1), ("y", "foo")}
  63. assert dir(cfg) == ["x", "y"]
  64. cfg.x = 2
  65. cfg["y"] = "bar"
  66. assert cfg["x"] == 2
  67. assert cfg.y == "bar"
  68. with pytest.raises(TypeError, match="can't be deleted"):
  69. del cfg.x
  70. with pytest.raises(TypeError, match="can't be deleted"):
  71. del cfg["y"]
  72. assert cfg.x == 2
  73. assert cfg == cfg
  74. assert cfg == ExampleConfig(x=2, y="bar")
  75. assert cfg != ExampleConfig(x=3, y="baz")
  76. assert cfg != Config(x=2, y="bar")
  77. with pytest.raises(TypeError, match="y must be a str"):
  78. cfg["y"] = 5
  79. with pytest.raises(ValueError, match="x must be positive"):
  80. cfg.x = -5
  81. assert cfg.get("x", 10) == 2
  82. with pytest.raises(AttributeError):
  83. cfg.z = 5
  84. with pytest.raises(KeyError):
  85. cfg["z"] = 5
  86. with pytest.raises(AttributeError):
  87. cfg.z
  88. with pytest.raises(KeyError):
  89. cfg["z"]
  90. cfg2 = pickle.loads(pickle.dumps(cfg))
  91. assert cfg == cfg2
  92. assert cfg.__doc__ == "Example configuration."
  93. assert cfg2.__doc__ == "Example configuration."
  94. def test_config_defaults():
  95. class DefaultConfig(Config):
  96. x: int = 0
  97. y: int
  98. cfg = DefaultConfig(y=1)
  99. assert cfg.x == 0
  100. cfg = DefaultConfig(x=2, y=1)
  101. assert cfg.x == 2
  102. def test_nxconfig():
  103. assert isinstance(nx.config.backend_priority, BackendPriorities)
  104. assert isinstance(nx.config.backend_priority.algos, list)
  105. assert isinstance(nx.config.backends, Config)
  106. with pytest.raises(TypeError, match="must be a list of backend names"):
  107. nx.config.backend_priority.algos = "nx_loopback"
  108. with pytest.raises(ValueError, match="Unknown backend when setting"):
  109. nx.config.backend_priority.algos = ["this_almost_certainly_is_not_a_backend"]
  110. with pytest.raises(TypeError, match="must be a Config of backend configs"):
  111. nx.config.backends = {}
  112. with pytest.raises(TypeError, match="must be a Config of backend configs"):
  113. nx.config.backends = Config(plausible_backend_name={})
  114. with pytest.raises(ValueError, match="Unknown backend when setting"):
  115. nx.config.backends = Config(this_almost_certainly_is_not_a_backend=Config())
  116. with pytest.raises(TypeError, match="must be True or False"):
  117. nx.config.cache_converted_graphs = "bad value"
  118. with pytest.raises(TypeError, match="must be a set of "):
  119. nx.config.warnings_to_ignore = 7
  120. with pytest.raises(ValueError, match="Unknown warning "):
  121. nx.config.warnings_to_ignore = {"bad value"}
  122. prev = nx.config.backend_priority
  123. try:
  124. nx.config.backend_priority = ["networkx"]
  125. assert isinstance(nx.config.backend_priority, BackendPriorities)
  126. assert nx.config.backend_priority.algos == ["networkx"]
  127. finally:
  128. nx.config.backend_priority = prev
  129. def test_nxconfig_context():
  130. # We do some special handling so that `nx.config.backend_priority = val`
  131. # actually does `nx.config.backend_priority.algos = val`.
  132. orig = nx.config.backend_priority.algos
  133. val = [] if orig else ["networkx"]
  134. assert orig != val
  135. assert nx.config.backend_priority.algos != val
  136. with nx.config(backend_priority=val):
  137. assert nx.config.backend_priority.algos == val
  138. assert nx.config.backend_priority.algos == orig
  139. with nx.config.backend_priority(algos=val):
  140. assert nx.config.backend_priority.algos == val
  141. assert nx.config.backend_priority.algos == orig
  142. bad = ["bad-backend"]
  143. with pytest.raises(ValueError, match="Unknown backend"):
  144. nx.config.backend_priority = bad
  145. with pytest.raises(ValueError, match="Unknown backend"):
  146. with nx.config(backend_priority=bad):
  147. pass
  148. with pytest.raises(ValueError, match="Unknown backend"):
  149. with nx.config.backend_priority(algos=bad):
  150. pass
  151. def test_not_strict():
  152. class FlexibleConfig(Config, strict=False):
  153. x: int
  154. cfg = FlexibleConfig(x=1)
  155. assert "_strict" not in cfg
  156. assert len(cfg) == 1
  157. assert list(cfg) == ["x"]
  158. assert list(cfg.keys()) == ["x"]
  159. assert list(cfg.values()) == [1]
  160. assert list(cfg.items()) == [("x", 1)]
  161. assert cfg.x == 1
  162. assert cfg["x"] == 1
  163. assert "x" in cfg
  164. assert hasattr(cfg, "x")
  165. assert "FlexibleConfig(x=1)" in repr(cfg)
  166. assert cfg == FlexibleConfig(x=1)
  167. del cfg.x
  168. assert "FlexibleConfig()" in repr(cfg)
  169. assert len(cfg) == 0
  170. assert not hasattr(cfg, "x")
  171. assert "x" not in cfg
  172. assert not hasattr(cfg, "y")
  173. assert "y" not in cfg
  174. cfg.y = 2
  175. assert len(cfg) == 1
  176. assert list(cfg) == ["y"]
  177. assert list(cfg.keys()) == ["y"]
  178. assert list(cfg.values()) == [2]
  179. assert list(cfg.items()) == [("y", 2)]
  180. assert cfg.y == 2
  181. assert cfg["y"] == 2
  182. assert hasattr(cfg, "y")
  183. assert "y" in cfg
  184. del cfg["y"]
  185. assert len(cfg) == 0
  186. assert list(cfg) == []
  187. with pytest.raises(AttributeError, match="y"):
  188. del cfg.y
  189. with pytest.raises(KeyError, match="y"):
  190. del cfg["y"]
  191. with pytest.raises(TypeError, match="missing 1 required keyword-only"):
  192. FlexibleConfig()
  193. # Be strict when first creating the config object
  194. with pytest.raises(TypeError, match="unexpected keyword argument 'y'"):
  195. FlexibleConfig(x=1, y=2)
  196. class FlexibleConfigWithDefault(Config, strict=False):
  197. x: int = 0
  198. assert FlexibleConfigWithDefault().x == 0
  199. assert FlexibleConfigWithDefault(x=1)["x"] == 1
  200. def test_context():
  201. cfg = Config(x=1)
  202. with cfg(x=2) as c:
  203. assert c.x == 2
  204. c.x = 3
  205. assert cfg.x == 3
  206. assert cfg.x == 1
  207. with cfg(x=2) as c:
  208. assert c == cfg
  209. assert cfg.x == 2
  210. with cfg(x=3) as c2:
  211. assert c2 == cfg
  212. assert cfg.x == 3
  213. with pytest.raises(RuntimeError, match="context manager without"):
  214. with cfg as c3: # Forgot to call `cfg(...)`
  215. pass
  216. assert cfg.x == 3
  217. assert cfg.x == 2
  218. assert cfg.x == 1
  219. c = cfg(x=4) # Not yet as context (not recommended, but possible)
  220. assert c == cfg
  221. assert cfg.x == 4
  222. # Cheat by looking at internal data; context stack should only grow with __enter__
  223. assert cfg._prev is not None
  224. assert cfg._context_stack == []
  225. with c:
  226. assert c == cfg
  227. assert cfg.x == 4
  228. assert cfg.x == 1
  229. # Cheat again; there was no preceding `cfg(...)` call this time
  230. assert cfg._prev is None
  231. with pytest.raises(RuntimeError, match="context manager without"):
  232. with cfg:
  233. pass
  234. assert cfg.x == 1