diff --git a/dcos/api/config.py b/dcos/api/config.py index cfab411..d9c890c 100644 --- a/dcos/api/config.py +++ b/dcos/api/config.py @@ -3,50 +3,162 @@ import collections import toml -class Toml(collections.MutableMapping): +def mutable_load_from_path(path): + """Loads a TOML file from the path - """Class for managing CLI configuration through TOML.""" + :param path: Path to the TOML file + :type path: str + :returns: Mutable map for the configuration file + :rtype: MutableToml + """ + + with open(path) as config_file: + return MutableToml(toml.loads(config_file.read())) + + +def load_from_path(path): + """Loads a TOML file from the path + + :param path: Path to the TOML file + :type path: str + :returns: Map for the configuration file + :rtype: Toml + """ + + with open(path) as config_file: + return Toml(toml.loads(config_file.read())) + + +def _get_path(config, path): + """ + :param path: Path to the value. E.g. 'path.to.value' + :type path: str + :returns: Value stored at the given path + :rtype: double, int, str, list or dict + """ + + for section in path.split('.'): + config = config[section] + + return config + + +def _iterator(parent, dictionary): + """ + :param parent: Path to the value parameter + :type parent: str + :param dictionary: Value of the key + :type dictionary: collection.Mapping + """ + + for key, value in dictionary.items(): + + new_key = key + if parent is not None: + new_key = "{}.{}".format(parent, key) + + if not isinstance(value, collections.Mapping): + yield (new_key, value) + else: + for x in _iterator(new_key, value): + yield x + + +class Toml(collections.Mapping): + """Class for getting value from TOML.""" def __init__(self, dictionary): - """Constructs interface for managing configurations - - :param dictionary: Configuration dictionary + """ + :param dictionary: configuration dictionary :type dictionary: dict """ self._dictionary = dictionary def __getitem__(self, path): - """Get the value for a path - + """ :param path: Path to the value. E.g. 'path.to.value' :type path: str :returns: Value stored at the given path :rtype: double, int, str, list or dict """ - config = self._dictionary - - for section in path.split('.'): - config = config[section] - - if isinstance(config, collections.MutableMapping): + config = _get_path(self._dictionary, path) + if isinstance(config, collections.Mapping): return Toml(config) else: return config def __iter__(self): - """Returns iterator - + """ :returns: Dictionary iterator :rtype: iterator """ return iter(self._dictionary) - def __len__(self): - """Length of the toml configuration + def property_items(self): + """Iterator for full-path keys and values + :returns: Iterator for pull-path keys and values + :rtype: iterator of tuples + """ + + return _iterator(None, self._dictionary) + + def __len__(self): + """ + :returns: The length of the dictionary + :rtype: int + """ + + return len(self._dictionary) + + +class MutableToml(collections.MutableMapping): + """Class for managing CLI configuration through TOML.""" + + def __init__(self, dictionary): + """ + :param dictionary: configuration dictionary + :type dictionary: dict + """ + + self._dictionary = dictionary + + def __getitem__(self, path): + """ + :param path: Path to the value. E.g. 'path.to.value' + :type path: str + :returns: Value stored at the given path + :rtype: double, int, str, list or dict + """ + + config = _get_path(self._dictionary, path) + if isinstance(config, collections.MutableMapping): + return MutableToml(config) + else: + return config + + def __iter__(self): + """ + :returns: Dictionary iterator + :rtype: iterator + """ + + return iter(self._dictionary) + + def property_items(self): + """Iterator for full-path keys and values + + :returns: Iterator for pull-path keys and values + :rtype: iterator of tuples + """ + + return _iterator(None, self._dictionary) + + def __len__(self): + """ :returns: The length of the dictionary :rtype: int """ @@ -54,8 +166,7 @@ class Toml(collections.MutableMapping): return len(self._dictionary) def __setitem__(self, path, value): - """Set the path to a value - + """ :param path: Path to set :type path: str :param value: Value to store @@ -66,13 +177,12 @@ class Toml(collections.MutableMapping): sections = path.split('.') for section in sections[:-1]: - config = config[section] + config = config.setdefault(section, {}) config[sections[-1]] = value def __delitem__(self, path): - """Delete value stored at path - + """ :param path: Path to delete :type path: str """ @@ -83,16 +193,3 @@ class Toml(collections.MutableMapping): config = config[section] del config[sections[-1]] - - @classmethod - def load_from_path(class_, path): - """Loads a TOML file from the path - - :param path: Path to the TOML file - :type path: str - :returns: Dictionary for the configuration file - :rtype: Toml - """ - - with open(path) as config_file: - return Toml(toml.loads(config_file.read())) diff --git a/dcos/cli/config/main.py b/dcos/cli/config/main.py index e18343f..9852a3d 100644 --- a/dcos/cli/config/main.py +++ b/dcos/cli/config/main.py @@ -3,6 +3,7 @@ Usage: dcos config info dcos config [] dcos config --unset + dcos config --list dcos config --help Options: @@ -10,11 +11,12 @@ Options: --unset Remove property from the config file """ +import collections import os import docopt import toml -from dcos.api import config, constants +from dcos.api import config, constants, options def main(): @@ -27,21 +29,33 @@ def main(): print('Get and set DCOS command line options') elif args['config'] and args['--unset']: - toml_config = config.Toml.load_from_path(config_path) - del toml_config[args['']] - _save_config_file(config_path, toml_config) + toml_config = config.mutable_load_from_path(config_path) + if toml_config.pop(args[''], None): + _save_config_file(config_path, toml_config) + + elif args['config'] and args['--list']: + toml_config = config.load_from_path(config_path) + for key, value in toml_config.property_items(): + print('{}={}'.format(key, value)) elif args['config'] and args[''] is None: - toml_config = config.Toml.load_from_path(config_path) - print(toml_config[args['']]) + toml_config = config.load_from_path(config_path) + value = toml_config.get(args['']) + if value is not None and not isinstance(value, collections.Mapping): + print(value) + else: + return 1 elif args['config']: - toml_config = config.Toml.load_from_path(config_path) + toml_config = config.mutable_load_from_path(config_path) toml_config[args['']] = args[''] _save_config_file(config_path, toml_config) else: - print(args) + print(options.make_generic_usage_error(__doc__)) + return 1 + + return 0 def _save_config_file(config_path, toml_config): @@ -51,6 +65,6 @@ def _save_config_file(config_path, toml_config): :type config_path: str or unicode """ - serial = toml.dumps(toml_config) + serial = toml.dumps(toml_config._dictionary) with open(config_path, 'w') as config_file: config_file.write(serial) diff --git a/tests/data/Dcos.toml b/tests/data/Dcos.toml index 218879f..24b2be1 100644 --- a/tests/data/Dcos.toml +++ b/tests/data/Dcos.toml @@ -1,3 +1,3 @@ [marathon] -port = 8080 host = "localhost" +port = 8080 diff --git a/tests/test_config.py b/tests/test_config.py index 203135b..81b95b4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,19 +4,35 @@ from dcos.api import config @pytest.fixture def conf(): - return config.Toml({ - 'dcos': { - 'user': 'principal', - 'mesos_uri': 'zk://localhost/mesos' - }, - 'package': { - 'repo_uri': 'git://localhost/mesosphere/package-repo.git' - } + return config.Toml(_conf()) + + +def test_get_property(conf): + conf['dcos.mesos_uri'] == 'zk://localhost/mesos' + + +def test_get_partial_property(conf): + conf['dcos'] == config.Toml({ + 'user': 'group', + 'mesos_uri': 'zk://localhost/mesos' }) -def test_unset_property(conf): - expect = config.Toml({ +def test_iterator(conf): + assert (sorted(list(conf.property_items())) == [ + ('dcos.mesos_uri', 'zk://localhost/mesos'), + ('dcos.user', 'principal'), + ('package.repo_uri', 'git://localhost/mesosphere/package-repo.git'), + ]) + + +@pytest.fixture +def mutable_conf(): + return config.MutableToml(_conf()) + + +def test_mutable_unset_property(mutable_conf): + expect = config.MutableToml({ 'dcos': { 'user': 'principal', 'mesos_uri': 'zk://localhost/mesos' @@ -24,13 +40,13 @@ def test_unset_property(conf): 'package': {} }) - del conf['package.repo_uri'] + del mutable_conf['package.repo_uri'] - assert conf == expect + assert mutable_conf == expect -def test_set_property(conf): - expect = config.Toml({ +def test_mutable_set_property(mutable_conf): + expect = config.MutableToml({ 'dcos': { 'user': 'group', 'mesos_uri': 'zk://localhost/mesos' @@ -40,10 +56,56 @@ def test_set_property(conf): } }) - conf['dcos.user'] = 'group' + mutable_conf['dcos.user'] = 'group' - assert conf == expect + assert mutable_conf == expect -def test_get_property(conf): - conf['dcos.mesos_uri'] == 'zk://localhost/mesos' +def test_mutable_test_deep_property(mutable_conf): + expect = config.MutableToml({ + 'dcos': { + 'user': 'principal', + 'mesos_uri': 'zk://localhost/mesos' + }, + 'package': { + 'repo_uri': 'git://localhost/mesosphere/package-repo.git' + }, + 'new': { + 'key': 42 + }, + }) + + mutable_conf['new.key'] = 42 + + assert mutable_conf == expect + + +def test_mutable_get_property(mutable_conf): + mutable_conf['dcos.mesos_uri'] == 'zk://localhost/mesos' + + +def test_mutable_get_partial_property(mutable_conf): + mutable_conf['dcos'] == config.MutableToml({ + 'user': 'group', + 'mesos_uri': 'zk://localhost/mesos' + }) + + +def test_mutable_iterator(mutable_conf): + assert (sorted(list(mutable_conf.property_items())) == [ + ('dcos.mesos_uri', 'zk://localhost/mesos'), + ('dcos.user', 'principal'), + ('package.repo_uri', 'git://localhost/mesosphere/package-repo.git'), + ]) + + +def _conf(): + return { + 'dcos': { + 'user': 'principal', + 'mesos_uri': 'zk://localhost/mesos' + }, + 'package': { + 'repo_uri': 'git://localhost/mesosphere/package-repo.git' + } + }