test_config.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import warnings
  2. import pytest
  3. from pandas._config import config as cf
  4. from pandas._config.config import OptionError
  5. import pandas as pd
  6. class TestConfig:
  7. @pytest.fixture(autouse=True)
  8. def clean_config(self, monkeypatch):
  9. with monkeypatch.context() as m:
  10. m.setattr(cf, "_global_config", {})
  11. m.setattr(cf, "options", cf.DictWrapper(cf._global_config))
  12. m.setattr(cf, "_deprecated_options", {})
  13. m.setattr(cf, "_registered_options", {})
  14. # Our test fixture in conftest.py sets "chained_assignment"
  15. # to "raise" only after all test methods have been setup.
  16. # However, after this setup, there is no longer any
  17. # "chained_assignment" option, so re-register it.
  18. cf.register_option("chained_assignment", "raise")
  19. yield
  20. def test_api(self):
  21. # the pandas object exposes the user API
  22. assert hasattr(pd, "get_option")
  23. assert hasattr(pd, "set_option")
  24. assert hasattr(pd, "reset_option")
  25. assert hasattr(pd, "describe_option")
  26. def test_is_one_of_factory(self):
  27. v = cf.is_one_of_factory([None, 12])
  28. v(12)
  29. v(None)
  30. msg = r"Value must be one of None\|12"
  31. with pytest.raises(ValueError, match=msg):
  32. v(1.1)
  33. def test_register_option(self):
  34. cf.register_option("a", 1, "doc")
  35. # can't register an already registered option
  36. msg = "Option 'a' has already been registered"
  37. with pytest.raises(OptionError, match=msg):
  38. cf.register_option("a", 1, "doc")
  39. # can't register an already registered option
  40. msg = "Path prefix to option 'a' is already an option"
  41. with pytest.raises(OptionError, match=msg):
  42. cf.register_option("a.b.c.d1", 1, "doc")
  43. with pytest.raises(OptionError, match=msg):
  44. cf.register_option("a.b.c.d2", 1, "doc")
  45. # no python keywords
  46. msg = "for is a python keyword"
  47. with pytest.raises(ValueError, match=msg):
  48. cf.register_option("for", 0)
  49. with pytest.raises(ValueError, match=msg):
  50. cf.register_option("a.for.b", 0)
  51. # must be valid identifier (ensure attribute access works)
  52. msg = "oh my goddess! is not a valid identifier"
  53. with pytest.raises(ValueError, match=msg):
  54. cf.register_option("Oh my Goddess!", 0)
  55. # we can register options several levels deep
  56. # without predefining the intermediate steps
  57. # and we can define differently named options
  58. # in the same namespace
  59. cf.register_option("k.b.c.d1", 1, "doc")
  60. cf.register_option("k.b.c.d2", 1, "doc")
  61. def test_describe_option(self):
  62. cf.register_option("a", 1, "doc")
  63. cf.register_option("b", 1, "doc2")
  64. cf.deprecate_option("b")
  65. cf.register_option("c.d.e1", 1, "doc3")
  66. cf.register_option("c.d.e2", 1, "doc4")
  67. cf.register_option("f", 1)
  68. cf.register_option("g.h", 1)
  69. cf.register_option("k", 2)
  70. cf.deprecate_option("g.h", rkey="k")
  71. cf.register_option("l", "foo")
  72. # non-existent keys raise KeyError
  73. msg = r"No such keys\(s\)"
  74. with pytest.raises(OptionError, match=msg):
  75. cf.describe_option("no.such.key")
  76. # we can get the description for any key we registered
  77. assert "doc" in cf.describe_option("a", _print_desc=False)
  78. assert "doc2" in cf.describe_option("b", _print_desc=False)
  79. assert "precated" in cf.describe_option("b", _print_desc=False)
  80. assert "doc3" in cf.describe_option("c.d.e1", _print_desc=False)
  81. assert "doc4" in cf.describe_option("c.d.e2", _print_desc=False)
  82. # if no doc is specified we get a default message
  83. # saying "description not available"
  84. assert "available" in cf.describe_option("f", _print_desc=False)
  85. assert "available" in cf.describe_option("g.h", _print_desc=False)
  86. assert "precated" in cf.describe_option("g.h", _print_desc=False)
  87. assert "k" in cf.describe_option("g.h", _print_desc=False)
  88. # default is reported
  89. assert "foo" in cf.describe_option("l", _print_desc=False)
  90. # current value is reported
  91. assert "bar" not in cf.describe_option("l", _print_desc=False)
  92. cf.set_option("l", "bar")
  93. assert "bar" in cf.describe_option("l", _print_desc=False)
  94. def test_case_insensitive(self):
  95. cf.register_option("KanBAN", 1, "doc")
  96. assert "doc" in cf.describe_option("kanbaN", _print_desc=False)
  97. assert cf.get_option("kanBaN") == 1
  98. cf.set_option("KanBan", 2)
  99. assert cf.get_option("kAnBaN") == 2
  100. # gets of non-existent keys fail
  101. msg = r"No such keys\(s\): 'no_such_option'"
  102. with pytest.raises(OptionError, match=msg):
  103. cf.get_option("no_such_option")
  104. cf.deprecate_option("KanBan")
  105. assert cf._is_deprecated("kAnBaN")
  106. def test_get_option(self):
  107. cf.register_option("a", 1, "doc")
  108. cf.register_option("b.c", "hullo", "doc2")
  109. cf.register_option("b.b", None, "doc2")
  110. # gets of existing keys succeed
  111. assert cf.get_option("a") == 1
  112. assert cf.get_option("b.c") == "hullo"
  113. assert cf.get_option("b.b") is None
  114. # gets of non-existent keys fail
  115. msg = r"No such keys\(s\): 'no_such_option'"
  116. with pytest.raises(OptionError, match=msg):
  117. cf.get_option("no_such_option")
  118. def test_set_option(self):
  119. cf.register_option("a", 1, "doc")
  120. cf.register_option("b.c", "hullo", "doc2")
  121. cf.register_option("b.b", None, "doc2")
  122. assert cf.get_option("a") == 1
  123. assert cf.get_option("b.c") == "hullo"
  124. assert cf.get_option("b.b") is None
  125. cf.set_option("a", 2)
  126. cf.set_option("b.c", "wurld")
  127. cf.set_option("b.b", 1.1)
  128. assert cf.get_option("a") == 2
  129. assert cf.get_option("b.c") == "wurld"
  130. assert cf.get_option("b.b") == 1.1
  131. msg = r"No such keys\(s\): 'no.such.key'"
  132. with pytest.raises(OptionError, match=msg):
  133. cf.set_option("no.such.key", None)
  134. def test_set_option_empty_args(self):
  135. msg = "Must provide an even number of non-keyword arguments"
  136. with pytest.raises(ValueError, match=msg):
  137. cf.set_option()
  138. def test_set_option_uneven_args(self):
  139. msg = "Must provide an even number of non-keyword arguments"
  140. with pytest.raises(ValueError, match=msg):
  141. cf.set_option("a.b", 2, "b.c")
  142. def test_set_option_invalid_single_argument_type(self):
  143. msg = "Must provide an even number of non-keyword arguments"
  144. with pytest.raises(ValueError, match=msg):
  145. cf.set_option(2)
  146. def test_set_option_multiple(self):
  147. cf.register_option("a", 1, "doc")
  148. cf.register_option("b.c", "hullo", "doc2")
  149. cf.register_option("b.b", None, "doc2")
  150. assert cf.get_option("a") == 1
  151. assert cf.get_option("b.c") == "hullo"
  152. assert cf.get_option("b.b") is None
  153. cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
  154. assert cf.get_option("a") == "2"
  155. assert cf.get_option("b.c") is None
  156. assert cf.get_option("b.b") == 10.0
  157. def test_validation(self):
  158. cf.register_option("a", 1, "doc", validator=cf.is_int)
  159. cf.register_option("d", 1, "doc", validator=cf.is_nonnegative_int)
  160. cf.register_option("b.c", "hullo", "doc2", validator=cf.is_text)
  161. msg = "Value must have type '<class 'int'>'"
  162. with pytest.raises(ValueError, match=msg):
  163. cf.register_option("a.b.c.d2", "NO", "doc", validator=cf.is_int)
  164. cf.set_option("a", 2) # int is_int
  165. cf.set_option("b.c", "wurld") # str is_str
  166. cf.set_option("d", 2)
  167. cf.set_option("d", None) # non-negative int can be None
  168. # None not is_int
  169. with pytest.raises(ValueError, match=msg):
  170. cf.set_option("a", None)
  171. with pytest.raises(ValueError, match=msg):
  172. cf.set_option("a", "ab")
  173. msg = "Value must be a nonnegative integer or None"
  174. with pytest.raises(ValueError, match=msg):
  175. cf.register_option("a.b.c.d3", "NO", "doc", validator=cf.is_nonnegative_int)
  176. with pytest.raises(ValueError, match=msg):
  177. cf.register_option("a.b.c.d3", -2, "doc", validator=cf.is_nonnegative_int)
  178. msg = r"Value must be an instance of <class 'str'>\|<class 'bytes'>"
  179. with pytest.raises(ValueError, match=msg):
  180. cf.set_option("b.c", 1)
  181. validator = cf.is_one_of_factory([None, cf.is_callable])
  182. cf.register_option("b", lambda: None, "doc", validator=validator)
  183. # pylint: disable-next=consider-using-f-string
  184. cf.set_option("b", "%.1f".format) # Formatter is callable
  185. cf.set_option("b", None) # Formatter is none (default)
  186. with pytest.raises(ValueError, match="Value must be a callable"):
  187. cf.set_option("b", "%.1f")
  188. def test_reset_option(self):
  189. cf.register_option("a", 1, "doc", validator=cf.is_int)
  190. cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
  191. assert cf.get_option("a") == 1
  192. assert cf.get_option("b.c") == "hullo"
  193. cf.set_option("a", 2)
  194. cf.set_option("b.c", "wurld")
  195. assert cf.get_option("a") == 2
  196. assert cf.get_option("b.c") == "wurld"
  197. cf.reset_option("a")
  198. assert cf.get_option("a") == 1
  199. assert cf.get_option("b.c") == "wurld"
  200. cf.reset_option("b.c")
  201. assert cf.get_option("a") == 1
  202. assert cf.get_option("b.c") == "hullo"
  203. def test_reset_option_all(self):
  204. cf.register_option("a", 1, "doc", validator=cf.is_int)
  205. cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
  206. assert cf.get_option("a") == 1
  207. assert cf.get_option("b.c") == "hullo"
  208. cf.set_option("a", 2)
  209. cf.set_option("b.c", "wurld")
  210. assert cf.get_option("a") == 2
  211. assert cf.get_option("b.c") == "wurld"
  212. cf.reset_option("all")
  213. assert cf.get_option("a") == 1
  214. assert cf.get_option("b.c") == "hullo"
  215. def test_deprecate_option(self):
  216. # we can deprecate non-existent options
  217. cf.deprecate_option("foo")
  218. assert cf._is_deprecated("foo")
  219. with warnings.catch_warnings(record=True) as w:
  220. warnings.simplefilter("always")
  221. with pytest.raises(KeyError, match="No such keys.s.: 'foo'"):
  222. cf.get_option("foo")
  223. assert len(w) == 1 # should have raised one warning
  224. assert "deprecated" in str(w[-1]) # we get the default message
  225. cf.register_option("a", 1, "doc", validator=cf.is_int)
  226. cf.register_option("b.c", "hullo", "doc2")
  227. cf.register_option("foo", "hullo", "doc2")
  228. cf.deprecate_option("a", removal_ver="nifty_ver")
  229. with warnings.catch_warnings(record=True) as w:
  230. warnings.simplefilter("always")
  231. cf.get_option("a")
  232. assert len(w) == 1 # should have raised one warning
  233. assert "eprecated" in str(w[-1]) # we get the default message
  234. assert "nifty_ver" in str(w[-1]) # with the removal_ver quoted
  235. msg = "Option 'a' has already been defined as deprecated"
  236. with pytest.raises(OptionError, match=msg):
  237. cf.deprecate_option("a")
  238. cf.deprecate_option("b.c", "zounds!")
  239. with warnings.catch_warnings(record=True) as w:
  240. warnings.simplefilter("always")
  241. cf.get_option("b.c")
  242. assert len(w) == 1 # should have raised one warning
  243. assert "zounds!" in str(w[-1]) # we get the custom message
  244. # test rerouting keys
  245. cf.register_option("d.a", "foo", "doc2")
  246. cf.register_option("d.dep", "bar", "doc2")
  247. assert cf.get_option("d.a") == "foo"
  248. assert cf.get_option("d.dep") == "bar"
  249. cf.deprecate_option("d.dep", rkey="d.a") # reroute d.dep to d.a
  250. with warnings.catch_warnings(record=True) as w:
  251. warnings.simplefilter("always")
  252. assert cf.get_option("d.dep") == "foo"
  253. assert len(w) == 1 # should have raised one warning
  254. assert "eprecated" in str(w[-1]) # we get the custom message
  255. with warnings.catch_warnings(record=True) as w:
  256. warnings.simplefilter("always")
  257. cf.set_option("d.dep", "baz") # should overwrite "d.a"
  258. assert len(w) == 1 # should have raised one warning
  259. assert "eprecated" in str(w[-1]) # we get the custom message
  260. with warnings.catch_warnings(record=True) as w:
  261. warnings.simplefilter("always")
  262. assert cf.get_option("d.dep") == "baz"
  263. assert len(w) == 1 # should have raised one warning
  264. assert "eprecated" in str(w[-1]) # we get the custom message
  265. def test_config_prefix(self):
  266. with cf.config_prefix("base"):
  267. cf.register_option("a", 1, "doc1")
  268. cf.register_option("b", 2, "doc2")
  269. assert cf.get_option("a") == 1
  270. assert cf.get_option("b") == 2
  271. cf.set_option("a", 3)
  272. cf.set_option("b", 4)
  273. assert cf.get_option("a") == 3
  274. assert cf.get_option("b") == 4
  275. assert cf.get_option("base.a") == 3
  276. assert cf.get_option("base.b") == 4
  277. assert "doc1" in cf.describe_option("base.a", _print_desc=False)
  278. assert "doc2" in cf.describe_option("base.b", _print_desc=False)
  279. cf.reset_option("base.a")
  280. cf.reset_option("base.b")
  281. with cf.config_prefix("base"):
  282. assert cf.get_option("a") == 1
  283. assert cf.get_option("b") == 2
  284. def test_callback(self):
  285. k = [None]
  286. v = [None]
  287. def callback(key):
  288. k.append(key)
  289. v.append(cf.get_option(key))
  290. cf.register_option("d.a", "foo", cb=callback)
  291. cf.register_option("d.b", "foo", cb=callback)
  292. del k[-1], v[-1]
  293. cf.set_option("d.a", "fooz")
  294. assert k[-1] == "d.a"
  295. assert v[-1] == "fooz"
  296. del k[-1], v[-1]
  297. cf.set_option("d.b", "boo")
  298. assert k[-1] == "d.b"
  299. assert v[-1] == "boo"
  300. del k[-1], v[-1]
  301. cf.reset_option("d.b")
  302. assert k[-1] == "d.b"
  303. def test_set_ContextManager(self):
  304. def eq(val):
  305. assert cf.get_option("a") == val
  306. cf.register_option("a", 0)
  307. eq(0)
  308. with cf.option_context("a", 15):
  309. eq(15)
  310. with cf.option_context("a", 25):
  311. eq(25)
  312. eq(15)
  313. eq(0)
  314. cf.set_option("a", 17)
  315. eq(17)
  316. # Test that option_context can be used as a decorator too (#34253).
  317. @cf.option_context("a", 123)
  318. def f():
  319. eq(123)
  320. f()
  321. def test_attribute_access(self):
  322. holder = []
  323. def f3(key):
  324. holder.append(True)
  325. cf.register_option("a", 0)
  326. cf.register_option("c", 0, cb=f3)
  327. options = cf.options
  328. assert options.a == 0
  329. with cf.option_context("a", 15):
  330. assert options.a == 15
  331. options.a = 500
  332. assert cf.get_option("a") == 500
  333. cf.reset_option("a")
  334. assert options.a == cf.get_option("a", 0)
  335. msg = "You can only set the value of existing options"
  336. with pytest.raises(OptionError, match=msg):
  337. options.b = 1
  338. with pytest.raises(OptionError, match=msg):
  339. options.display = 1
  340. # make sure callback kicks when using this form of setting
  341. options.c = 1
  342. assert len(holder) == 1
  343. def test_option_context_scope(self):
  344. # Ensure that creating a context does not affect the existing
  345. # environment as it is supposed to be used with the `with` statement.
  346. # See https://github.com/pandas-dev/pandas/issues/8514
  347. original_value = 60
  348. context_value = 10
  349. option_name = "a"
  350. cf.register_option(option_name, original_value)
  351. # Ensure creating contexts didn't affect the current context.
  352. ctx = cf.option_context(option_name, context_value)
  353. assert cf.get_option(option_name) == original_value
  354. # Ensure the correct value is available inside the context.
  355. with ctx:
  356. assert cf.get_option(option_name) == context_value
  357. # Ensure the current context is reset
  358. assert cf.get_option(option_name) == original_value
  359. def test_dictwrapper_getattr(self):
  360. options = cf.options
  361. # GH 19789
  362. with pytest.raises(OptionError, match="No such option"):
  363. options.bananas
  364. assert not hasattr(options, "bananas")