123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- import warnings
- import pytest
- from pandas._config import config as cf
- from pandas._config.config import OptionError
- import pandas as pd
- class TestConfig:
- @pytest.fixture(autouse=True)
- def clean_config(self, monkeypatch):
- with monkeypatch.context() as m:
- m.setattr(cf, "_global_config", {})
- m.setattr(cf, "options", cf.DictWrapper(cf._global_config))
- m.setattr(cf, "_deprecated_options", {})
- m.setattr(cf, "_registered_options", {})
- # Our test fixture in conftest.py sets "chained_assignment"
- # to "raise" only after all test methods have been setup.
- # However, after this setup, there is no longer any
- # "chained_assignment" option, so re-register it.
- cf.register_option("chained_assignment", "raise")
- yield
- def test_api(self):
- # the pandas object exposes the user API
- assert hasattr(pd, "get_option")
- assert hasattr(pd, "set_option")
- assert hasattr(pd, "reset_option")
- assert hasattr(pd, "describe_option")
- def test_is_one_of_factory(self):
- v = cf.is_one_of_factory([None, 12])
- v(12)
- v(None)
- msg = r"Value must be one of None\|12"
- with pytest.raises(ValueError, match=msg):
- v(1.1)
- def test_register_option(self):
- cf.register_option("a", 1, "doc")
- # can't register an already registered option
- msg = "Option 'a' has already been registered"
- with pytest.raises(OptionError, match=msg):
- cf.register_option("a", 1, "doc")
- # can't register an already registered option
- msg = "Path prefix to option 'a' is already an option"
- with pytest.raises(OptionError, match=msg):
- cf.register_option("a.b.c.d1", 1, "doc")
- with pytest.raises(OptionError, match=msg):
- cf.register_option("a.b.c.d2", 1, "doc")
- # no python keywords
- msg = "for is a python keyword"
- with pytest.raises(ValueError, match=msg):
- cf.register_option("for", 0)
- with pytest.raises(ValueError, match=msg):
- cf.register_option("a.for.b", 0)
- # must be valid identifier (ensure attribute access works)
- msg = "oh my goddess! is not a valid identifier"
- with pytest.raises(ValueError, match=msg):
- cf.register_option("Oh my Goddess!", 0)
- # we can register options several levels deep
- # without predefining the intermediate steps
- # and we can define differently named options
- # in the same namespace
- cf.register_option("k.b.c.d1", 1, "doc")
- cf.register_option("k.b.c.d2", 1, "doc")
- def test_describe_option(self):
- cf.register_option("a", 1, "doc")
- cf.register_option("b", 1, "doc2")
- cf.deprecate_option("b")
- cf.register_option("c.d.e1", 1, "doc3")
- cf.register_option("c.d.e2", 1, "doc4")
- cf.register_option("f", 1)
- cf.register_option("g.h", 1)
- cf.register_option("k", 2)
- cf.deprecate_option("g.h", rkey="k")
- cf.register_option("l", "foo")
- # non-existent keys raise KeyError
- msg = r"No such keys\(s\)"
- with pytest.raises(OptionError, match=msg):
- cf.describe_option("no.such.key")
- # we can get the description for any key we registered
- assert "doc" in cf.describe_option("a", _print_desc=False)
- assert "doc2" in cf.describe_option("b", _print_desc=False)
- assert "precated" in cf.describe_option("b", _print_desc=False)
- assert "doc3" in cf.describe_option("c.d.e1", _print_desc=False)
- assert "doc4" in cf.describe_option("c.d.e2", _print_desc=False)
- # if no doc is specified we get a default message
- # saying "description not available"
- assert "available" in cf.describe_option("f", _print_desc=False)
- assert "available" in cf.describe_option("g.h", _print_desc=False)
- assert "precated" in cf.describe_option("g.h", _print_desc=False)
- assert "k" in cf.describe_option("g.h", _print_desc=False)
- # default is reported
- assert "foo" in cf.describe_option("l", _print_desc=False)
- # current value is reported
- assert "bar" not in cf.describe_option("l", _print_desc=False)
- cf.set_option("l", "bar")
- assert "bar" in cf.describe_option("l", _print_desc=False)
- def test_case_insensitive(self):
- cf.register_option("KanBAN", 1, "doc")
- assert "doc" in cf.describe_option("kanbaN", _print_desc=False)
- assert cf.get_option("kanBaN") == 1
- cf.set_option("KanBan", 2)
- assert cf.get_option("kAnBaN") == 2
- # gets of non-existent keys fail
- msg = r"No such keys\(s\): 'no_such_option'"
- with pytest.raises(OptionError, match=msg):
- cf.get_option("no_such_option")
- cf.deprecate_option("KanBan")
- assert cf._is_deprecated("kAnBaN")
- def test_get_option(self):
- cf.register_option("a", 1, "doc")
- cf.register_option("b.c", "hullo", "doc2")
- cf.register_option("b.b", None, "doc2")
- # gets of existing keys succeed
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- assert cf.get_option("b.b") is None
- # gets of non-existent keys fail
- msg = r"No such keys\(s\): 'no_such_option'"
- with pytest.raises(OptionError, match=msg):
- cf.get_option("no_such_option")
- def test_set_option(self):
- cf.register_option("a", 1, "doc")
- cf.register_option("b.c", "hullo", "doc2")
- cf.register_option("b.b", None, "doc2")
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- assert cf.get_option("b.b") is None
- cf.set_option("a", 2)
- cf.set_option("b.c", "wurld")
- cf.set_option("b.b", 1.1)
- assert cf.get_option("a") == 2
- assert cf.get_option("b.c") == "wurld"
- assert cf.get_option("b.b") == 1.1
- msg = r"No such keys\(s\): 'no.such.key'"
- with pytest.raises(OptionError, match=msg):
- cf.set_option("no.such.key", None)
- def test_set_option_empty_args(self):
- msg = "Must provide an even number of non-keyword arguments"
- with pytest.raises(ValueError, match=msg):
- cf.set_option()
- def test_set_option_uneven_args(self):
- msg = "Must provide an even number of non-keyword arguments"
- with pytest.raises(ValueError, match=msg):
- cf.set_option("a.b", 2, "b.c")
- def test_set_option_invalid_single_argument_type(self):
- msg = "Must provide an even number of non-keyword arguments"
- with pytest.raises(ValueError, match=msg):
- cf.set_option(2)
- def test_set_option_multiple(self):
- cf.register_option("a", 1, "doc")
- cf.register_option("b.c", "hullo", "doc2")
- cf.register_option("b.b", None, "doc2")
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- assert cf.get_option("b.b") is None
- cf.set_option("a", "2", "b.c", None, "b.b", 10.0)
- assert cf.get_option("a") == "2"
- assert cf.get_option("b.c") is None
- assert cf.get_option("b.b") == 10.0
- def test_validation(self):
- cf.register_option("a", 1, "doc", validator=cf.is_int)
- cf.register_option("d", 1, "doc", validator=cf.is_nonnegative_int)
- cf.register_option("b.c", "hullo", "doc2", validator=cf.is_text)
- msg = "Value must have type '<class 'int'>'"
- with pytest.raises(ValueError, match=msg):
- cf.register_option("a.b.c.d2", "NO", "doc", validator=cf.is_int)
- cf.set_option("a", 2) # int is_int
- cf.set_option("b.c", "wurld") # str is_str
- cf.set_option("d", 2)
- cf.set_option("d", None) # non-negative int can be None
- # None not is_int
- with pytest.raises(ValueError, match=msg):
- cf.set_option("a", None)
- with pytest.raises(ValueError, match=msg):
- cf.set_option("a", "ab")
- msg = "Value must be a nonnegative integer or None"
- with pytest.raises(ValueError, match=msg):
- cf.register_option("a.b.c.d3", "NO", "doc", validator=cf.is_nonnegative_int)
- with pytest.raises(ValueError, match=msg):
- cf.register_option("a.b.c.d3", -2, "doc", validator=cf.is_nonnegative_int)
- msg = r"Value must be an instance of <class 'str'>\|<class 'bytes'>"
- with pytest.raises(ValueError, match=msg):
- cf.set_option("b.c", 1)
- validator = cf.is_one_of_factory([None, cf.is_callable])
- cf.register_option("b", lambda: None, "doc", validator=validator)
- # pylint: disable-next=consider-using-f-string
- cf.set_option("b", "%.1f".format) # Formatter is callable
- cf.set_option("b", None) # Formatter is none (default)
- with pytest.raises(ValueError, match="Value must be a callable"):
- cf.set_option("b", "%.1f")
- def test_reset_option(self):
- cf.register_option("a", 1, "doc", validator=cf.is_int)
- cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- cf.set_option("a", 2)
- cf.set_option("b.c", "wurld")
- assert cf.get_option("a") == 2
- assert cf.get_option("b.c") == "wurld"
- cf.reset_option("a")
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "wurld"
- cf.reset_option("b.c")
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- def test_reset_option_all(self):
- cf.register_option("a", 1, "doc", validator=cf.is_int)
- cf.register_option("b.c", "hullo", "doc2", validator=cf.is_str)
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- cf.set_option("a", 2)
- cf.set_option("b.c", "wurld")
- assert cf.get_option("a") == 2
- assert cf.get_option("b.c") == "wurld"
- cf.reset_option("all")
- assert cf.get_option("a") == 1
- assert cf.get_option("b.c") == "hullo"
- def test_deprecate_option(self):
- # we can deprecate non-existent options
- cf.deprecate_option("foo")
- assert cf._is_deprecated("foo")
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- with pytest.raises(KeyError, match="No such keys.s.: 'foo'"):
- cf.get_option("foo")
- assert len(w) == 1 # should have raised one warning
- assert "deprecated" in str(w[-1]) # we get the default message
- cf.register_option("a", 1, "doc", validator=cf.is_int)
- cf.register_option("b.c", "hullo", "doc2")
- cf.register_option("foo", "hullo", "doc2")
- cf.deprecate_option("a", removal_ver="nifty_ver")
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- cf.get_option("a")
- assert len(w) == 1 # should have raised one warning
- assert "eprecated" in str(w[-1]) # we get the default message
- assert "nifty_ver" in str(w[-1]) # with the removal_ver quoted
- msg = "Option 'a' has already been defined as deprecated"
- with pytest.raises(OptionError, match=msg):
- cf.deprecate_option("a")
- cf.deprecate_option("b.c", "zounds!")
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- cf.get_option("b.c")
- assert len(w) == 1 # should have raised one warning
- assert "zounds!" in str(w[-1]) # we get the custom message
- # test rerouting keys
- cf.register_option("d.a", "foo", "doc2")
- cf.register_option("d.dep", "bar", "doc2")
- assert cf.get_option("d.a") == "foo"
- assert cf.get_option("d.dep") == "bar"
- cf.deprecate_option("d.dep", rkey="d.a") # reroute d.dep to d.a
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- assert cf.get_option("d.dep") == "foo"
- assert len(w) == 1 # should have raised one warning
- assert "eprecated" in str(w[-1]) # we get the custom message
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- cf.set_option("d.dep", "baz") # should overwrite "d.a"
- assert len(w) == 1 # should have raised one warning
- assert "eprecated" in str(w[-1]) # we get the custom message
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always")
- assert cf.get_option("d.dep") == "baz"
- assert len(w) == 1 # should have raised one warning
- assert "eprecated" in str(w[-1]) # we get the custom message
- def test_config_prefix(self):
- with cf.config_prefix("base"):
- cf.register_option("a", 1, "doc1")
- cf.register_option("b", 2, "doc2")
- assert cf.get_option("a") == 1
- assert cf.get_option("b") == 2
- cf.set_option("a", 3)
- cf.set_option("b", 4)
- assert cf.get_option("a") == 3
- assert cf.get_option("b") == 4
- assert cf.get_option("base.a") == 3
- assert cf.get_option("base.b") == 4
- assert "doc1" in cf.describe_option("base.a", _print_desc=False)
- assert "doc2" in cf.describe_option("base.b", _print_desc=False)
- cf.reset_option("base.a")
- cf.reset_option("base.b")
- with cf.config_prefix("base"):
- assert cf.get_option("a") == 1
- assert cf.get_option("b") == 2
- def test_callback(self):
- k = [None]
- v = [None]
- def callback(key):
- k.append(key)
- v.append(cf.get_option(key))
- cf.register_option("d.a", "foo", cb=callback)
- cf.register_option("d.b", "foo", cb=callback)
- del k[-1], v[-1]
- cf.set_option("d.a", "fooz")
- assert k[-1] == "d.a"
- assert v[-1] == "fooz"
- del k[-1], v[-1]
- cf.set_option("d.b", "boo")
- assert k[-1] == "d.b"
- assert v[-1] == "boo"
- del k[-1], v[-1]
- cf.reset_option("d.b")
- assert k[-1] == "d.b"
- def test_set_ContextManager(self):
- def eq(val):
- assert cf.get_option("a") == val
- cf.register_option("a", 0)
- eq(0)
- with cf.option_context("a", 15):
- eq(15)
- with cf.option_context("a", 25):
- eq(25)
- eq(15)
- eq(0)
- cf.set_option("a", 17)
- eq(17)
- # Test that option_context can be used as a decorator too (#34253).
- @cf.option_context("a", 123)
- def f():
- eq(123)
- f()
- def test_attribute_access(self):
- holder = []
- def f3(key):
- holder.append(True)
- cf.register_option("a", 0)
- cf.register_option("c", 0, cb=f3)
- options = cf.options
- assert options.a == 0
- with cf.option_context("a", 15):
- assert options.a == 15
- options.a = 500
- assert cf.get_option("a") == 500
- cf.reset_option("a")
- assert options.a == cf.get_option("a", 0)
- msg = "You can only set the value of existing options"
- with pytest.raises(OptionError, match=msg):
- options.b = 1
- with pytest.raises(OptionError, match=msg):
- options.display = 1
- # make sure callback kicks when using this form of setting
- options.c = 1
- assert len(holder) == 1
- def test_option_context_scope(self):
- # Ensure that creating a context does not affect the existing
- # environment as it is supposed to be used with the `with` statement.
- # See https://github.com/pandas-dev/pandas/issues/8514
- original_value = 60
- context_value = 10
- option_name = "a"
- cf.register_option(option_name, original_value)
- # Ensure creating contexts didn't affect the current context.
- ctx = cf.option_context(option_name, context_value)
- assert cf.get_option(option_name) == original_value
- # Ensure the correct value is available inside the context.
- with ctx:
- assert cf.get_option(option_name) == context_value
- # Ensure the current context is reset
- assert cf.get_option(option_name) == original_value
- def test_dictwrapper_getattr(self):
- options = cf.options
- # GH 19789
- with pytest.raises(OptionError, match="No such option"):
- options.bananas
- assert not hasattr(options, "bananas")
|