configuration: add delete_config()

We currently have no way to delete options once set. This is not good
for users that want to delete things so they go back to their default
values. Add a library API and tests for this; we'll extend the config
command with options that use them next.

Signed-off-by: Marti Bolivar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Marti Bolivar 2019-05-24 22:49:20 -06:00
parent 4636b3def2
commit 4fe9d4a042
2 changed files with 154 additions and 0 deletions

View File

@ -118,6 +118,53 @@ def update_config(section, key, value, configfile=ConfigFile.LOCAL):
updater[section][key] = value
updater.write()
def delete_config(section, key, configfile=None):
'''Delete the option section.key from the given file or files.
:param section: section whose key to delete
:param key: key to delete
:param configfile: If ConfigFile.ALL, delete section.key in all files
where it is set.
If None, delete only from the highest-precedence
global or local file where it is set, allowing
lower-precedence values to take effect again.
If a list of ConfigFile enumerators, delete
from those files.
Otherwise, delete from the given ConfigFile.
Deleting the only key in a section deletes the entire section.
If the option is not set, KeyError is raised.'''
stop = False
if configfile is None:
to_check = [_location(x) for x in
[ConfigFile.LOCAL, ConfigFile.GLOBAL]]
stop = True
elif configfile == ConfigFile.ALL:
to_check = [_location(x) for x in
[ConfigFile.SYSTEM, ConfigFile.GLOBAL, ConfigFile.LOCAL]]
elif isinstance(configfile, ConfigFile):
to_check = [_location(configfile)]
else:
to_check = [_location(x) for x in configfile]
found = False
for path in to_check:
cobj = configobj.ConfigObj(path)
if section not in cobj or key not in cobj[section]:
continue
del cobj[section][key]
if not cobj[section].items():
del cobj[section]
cobj.write()
found = True
if stop:
break
if not found:
raise KeyError('{}.{}'.format(section, key))
def _location(cfg):
# Making this a function that gets called each time you ask for a
# configuration file makes it respect updated environment

View File

@ -235,6 +235,113 @@ def test_local_creation():
assert 'pytest' not in cfg(f=GLOBAL)
assert cfg(f=LOCAL)['pytest']['key'] == 'val'
@patch('west.configuration._location', new=tstloc)
def test_delete_basic():
# Basic deletion test: write local, verify global and system deletions
# don't work, then delete local does work.
config.update_config('pytest', 'key', 'val', configfile=LOCAL)
assert cfg(f=ALL)['pytest']['key'] == 'val'
with pytest.raises(KeyError):
config.delete_config('pytest', 'key', configfile=SYSTEM)
with pytest.raises(KeyError):
config.delete_config('pytest', 'key', configfile=GLOBAL)
config.delete_config('pytest', 'key', configfile=LOCAL)
assert 'pytest' not in cfg(f=ALL)
@patch('west.configuration._location', new=tstloc)
def test_delete_all():
# Deleting ConfigFile.ALL should delete from everywhere.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=ALL)
assert 'pytest' not in cfg(f=ALL)
@patch('west.configuration._location', new=tstloc)
def test_delete_none():
# Deleting None should delete from lowest-precedence global or
# local file only.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=None)
assert cfg(f=ALL)['pytest']['key'] == 'global'
config.delete_config('pytest', 'key', configfile=None)
assert cfg(f=ALL)['pytest']['key'] == 'system'
with pytest.raises(KeyError):
config.delete_config('pytest', 'key', configfile=None)
@patch('west.configuration._location', new=tstloc)
def test_delete_list():
# Test delete of a list of places.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=[GLOBAL, LOCAL])
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert 'pytest' not in cfg(f=GLOBAL)
assert 'pytest' not in cfg(f=LOCAL)
@patch('west.configuration._location', new=tstloc)
def test_delete_system():
# Test SYSTEM-only delete.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=SYSTEM)
assert 'pytest' not in cfg(f=SYSTEM)
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
@patch('west.configuration._location', new=tstloc)
def test_delete_global():
# Test GLOBAL-only delete.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=GLOBAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert 'pytest' not in cfg(f=GLOBAL)
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
@patch('west.configuration._location', new=tstloc)
def test_delete_local():
# Test LOCAL-only delete.
config.update_config('pytest', 'key', 'system', configfile=SYSTEM)
config.update_config('pytest', 'key', 'global', configfile=GLOBAL)
config.update_config('pytest', 'key', 'local', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert cfg(f=LOCAL)['pytest']['key'] == 'local'
config.delete_config('pytest', 'key', configfile=LOCAL)
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
assert 'pytest' not in cfg(f=LOCAL)
@patch('west.configuration._location', new=tstloc)
def test_delete_local_one():
# Test LOCAL-only delete of one option doesn't affect the other.
config.update_config('pytest', 'key1', 'foo', configfile=LOCAL)
config.update_config('pytest', 'key2', 'bar', configfile=LOCAL)
config.delete_config('pytest', 'key1', configfile=LOCAL)
assert 'pytest' in cfg(f=LOCAL)
assert cfg(f=LOCAL)['pytest']['key2'] == 'bar'
def test_default_config():
# Writing to a value without a config destination should default
# to --local.