diff --git a/dvc/cache.py b/dvc/cache.py index 48432ea3b..c834ed0ae 100644 --- a/dvc/cache.py +++ b/dvc/cache.py @@ -7,6 +7,20 @@ import os from dvc.config import Config +class CacheConfig(object): + def __init__(self, config): + self.config = config + + def set_dir(self, dname, level=None): + from dvc.remote.config import RemoteConfig + + configobj = self.config.get_configobj(level) + path = RemoteConfig.resolve_path(dname, configobj.filename) + self.config.set( + Config.SECTION_CACHE, Config.SECTION_CACHE_DIR, path, level=level + ) + + class Cache(object): """Class that manages cache locations of a dvc repo. diff --git a/dvc/command/cache.py b/dvc/command/cache.py index c2ac03d20..b774aca8b 100644 --- a/dvc/command/cache.py +++ b/dvc/command/cache.py @@ -4,12 +4,16 @@ import argparse from dvc.command.base import fix_subparsers, append_doc_link from dvc.command.config import CmdConfig -from dvc.remote.config import set_cache_dir +from dvc.cache import CacheConfig class CmdCacheDir(CmdConfig): + def __init__(self, args): + super(CmdCacheDir, self).__init__(args) + self.config = CacheConfig(self.config) + def run(self): - set_cache_dir(self.config, self.args.value, level=self.args.level) + self.config.set_dir(self.args.value, level=self.args.level) return 0 diff --git a/dvc/command/remote.py b/dvc/command/remote.py index 2fe5b1f92..df1251d0e 100644 --- a/dvc/command/remote.py +++ b/dvc/command/remote.py @@ -5,16 +5,20 @@ import logging from dvc.command.base import append_doc_link, fix_subparsers from dvc.command.config import CmdConfig -from dvc.remote.config import remote_add, remote_remove, remote_modify -from dvc.remote.config import remote_list, remote_default +from dvc.remote.config import RemoteConfig logger = logging.getLogger(__name__) -class CmdRemoteAdd(CmdConfig): +class CmdRemoteConfig(CmdConfig): + def __init__(self, args): + super(CmdRemoteConfig, self).__init__(args) + self.config = RemoteConfig(self.config) + + +class CmdRemoteAdd(CmdRemoteConfig): def run(self): - remote_add( - self.config, + self.config.add( self.args.name, self.args.url, force=self.args.force, @@ -24,16 +28,15 @@ class CmdRemoteAdd(CmdConfig): return 0 -class CmdRemoteRemove(CmdConfig): +class CmdRemoteRemove(CmdRemoteConfig): def run(self): - remote_remove(self.config, self.args.name, level=self.args.level) + self.config.remove(self.args.name, level=self.args.level) return 0 -class CmdRemoteModify(CmdConfig): +class CmdRemoteModify(CmdRemoteConfig): def run(self): - remote_modify( - self.config, + self.config.modify( self.args.name, self.args.option, self.args.value, @@ -42,21 +45,17 @@ class CmdRemoteModify(CmdConfig): return 0 -class CmdRemoteDefault(CmdConfig): +class CmdRemoteDefault(CmdRemoteConfig): def run(self): - remote_default( - self.config, - self.args.name, - unset=self.args.unset, - level=self.args.level, + self.config.set_default( + self.args.name, unset=self.args.unset, level=self.args.level ) return 0 -class CmdRemoteList(CmdConfig): +class CmdRemoteList(CmdRemoteConfig): def run(self): - remotes = remote_list(self.config, level=self.args.level) - for name, url in remotes.items(): + for name, url in self.config.list(level=self.args.level).items(): logger.info("{}\t{}".format(name, url)) return 0 diff --git a/dvc/config.py b/dvc/config.py index 555a8ca15..8222bf093 100644 --- a/dvc/config.py +++ b/dvc/config.py @@ -534,7 +534,7 @@ class Config(object): # pylint: disable=too-many-instance-attributes config[section][opt] = value self.save(config) - def get(self, section, opt, level=None): + def get(self, section, opt=None, level=None): """Return option value from the config. Args: diff --git a/dvc/remote/__init__.py b/dvc/remote/__init__.py index f5ed528e0..e8ffe81f4 100644 --- a/dvc/remote/__init__.py +++ b/dvc/remote/__init__.py @@ -10,7 +10,7 @@ from dvc.remote.http import RemoteHTTP from dvc.remote.https import RemoteHTTPS from dvc.remote.oss import RemoteOSS -from .config import get_remote_settings +from .config import RemoteConfig REMOTES = [ @@ -36,7 +36,8 @@ def _get(config): def Remote(repo, **kwargs): name = kwargs.get("name") if name: - settings = get_remote_settings(repo.config, name) + remote_config = RemoteConfig(repo.config) + settings = remote_config.get_settings(name) else: settings = kwargs return _get(settings)(repo, settings) diff --git a/dvc/remote/config.py b/dvc/remote/config.py index 5b8672330..54b33ed2b 100644 --- a/dvc/remote/config.py +++ b/dvc/remote/config.py @@ -9,147 +9,143 @@ from dvc.config import Config, ConfigError logger = logging.getLogger(__name__) -def get_remote_settings(config, name): - """ - Args: - name (str): The name of the remote that we want to retrieve +class RemoteConfig(object): + def __init__(self, config): + self.config = config - Returns: - dict: The content beneath the given remote name. + def get_settings(self, name): + """ + Args: + name (str): The name of the remote that we want to retrieve - Example: - >>> config = {'remote "server"': {'url': 'ssh://localhost/'}} - >>> get_remote_settings("server") - {'url': 'ssh://localhost/'} - """ - settings = config.config.get( - config.SECTION_REMOTE_FMT.format(name.lower()) - ) + Returns: + dict: The content beneath the given remote name. - if settings is None: - raise ConfigError("unable to find remote section '{}'".format(name)) - - parsed = urlparse(settings["url"]) - - # Support for cross referenced remotes. - # This will merge the settings, giving priority to the outer reference. - # For example, having: - # - # dvc remote add server ssh://localhost - # dvc remote modify server user root - # dvc remote modify server ask_password true - # - # dvc remote add images remote://server/tmp/pictures - # dvc remote modify images user alice - # dvc remote modify images ask_password false - # dvc remote modify images password asdf1234 - # - # Results on a config dictionary like: - # - # { - # "url": "ssh://localhost/tmp/pictures", - # "user": "alice", - # "password": "asdf1234", - # "ask_password": False, - # } - # - if parsed.scheme == "remote": - reference = get_remote_settings(config, parsed.netloc) - url = posixpath.join(reference["url"], parsed.path.lstrip("/")) - merged = reference.copy() - merged.update(settings) - merged["url"] = url - return merged - - return settings - - -def _resolve_remote_path(path, config_file): - """Resolve path relative to config file location. - - Args: - path: Path to be resolved. - config_file: Path to config file, which `path` is specified - relative to. - - Returns: - Path relative to the `config_file` location. If `path` is an - absolute path then it will be returned without change. - - """ - if os.path.isabs(path): - return path - return os.path.relpath(path, os.path.dirname(config_file)) - - -def remote_add(config, name, url, default=False, force=False, level=None): - from dvc.remote import _get, RemoteLOCAL - - configobj = config.get_configobj(level) - remote = _get({Config.SECTION_REMOTE_URL: url}) - if remote == RemoteLOCAL and not url.startswith("remote://"): - url = _resolve_remote_path(url, configobj.filename) - - config.set( - config.SECTION_REMOTE_FMT.format(name), - config.SECTION_REMOTE_URL, - url, - force=force, - level=level, - ) - if default: - logger.info("Setting '{}' as a default remote.".format(name)) - config.set( - config.SECTION_CORE, config.SECTION_CORE_REMOTE, name, level=level + Example: + >>> config = {'remote "server"': {'url': 'ssh://localhost/'}} + >>> get_settings("server") + {'url': 'ssh://localhost/'} + """ + settings = self.config.config.get( + Config.SECTION_REMOTE_FMT.format(name.lower()) ) + if settings is None: + raise ConfigError( + "unable to find remote section '{}'".format(name) + ) -def remote_remove(config, name, level=None): - config.unset(config.SECTION_REMOTE_FMT.format(name), level=level) + parsed = urlparse(settings["url"]) - if level is None: - level = config.LEVEL_REPO + # Support for cross referenced remotes. + # This will merge the settings, giving priority to the outer reference. + # For example, having: + # + # dvc remote add server ssh://localhost + # dvc remote modify server user root + # dvc remote modify server ask_password true + # + # dvc remote add images remote://server/tmp/pictures + # dvc remote modify images user alice + # dvc remote modify images ask_password false + # dvc remote modify images password asdf1234 + # + # Results on a config dictionary like: + # + # { + # "url": "ssh://localhost/tmp/pictures", + # "user": "alice", + # "password": "asdf1234", + # "ask_password": False, + # } + # + if parsed.scheme == "remote": + reference = self.get_settings(parsed.netloc) + url = posixpath.join(reference["url"], parsed.path.lstrip("/")) + merged = reference.copy() + merged.update(settings) + merged["url"] = url + return merged - for lev in [ - config.LEVEL_LOCAL, - config.LEVEL_REPO, - config.LEVEL_GLOBAL, - config.LEVEL_SYSTEM, - ]: - config.unset( - config.SECTION_CORE, - config.SECTION_CORE_REMOTE, - level=lev, - force=True, + return settings + + @staticmethod + def resolve_path(path, config_file): + """Resolve path relative to config file location. + + Args: + path: Path to be resolved. + config_file: Path to config file, which `path` is specified + relative to. + + Returns: + Path relative to the `config_file` location. If `path` is an + absolute path then it will be returned without change. + + """ + if os.path.isabs(path): + return path + return os.path.relpath(path, os.path.dirname(config_file)) + + def add(self, name, url, default=False, force=False, level=None): + from dvc.remote import _get, RemoteLOCAL + + configobj = self.config.get_configobj(level) + remote = _get({Config.SECTION_REMOTE_URL: url}) + if remote == RemoteLOCAL and not url.startswith("remote://"): + url = self.resolve_path(url, configobj.filename) + + self.config.set( + Config.SECTION_REMOTE_FMT.format(name), + Config.SECTION_REMOTE_URL, + url, + force=force, + level=level, ) - if lev == level: - break + if default: + logger.info("Setting '{}' as a default remote.".format(name)) + self.config.set( + Config.SECTION_CORE, + Config.SECTION_CORE_REMOTE, + name, + level=level, + ) + def remove(self, name, level=None): + self.config.unset(Config.SECTION_REMOTE_FMT.format(name), level=level) -def remote_modify(config, name, option, value, level=None): - config.set( - config.SECTION_REMOTE_FMT.format(name), option, value, level=level - ) + if level is None: + level = Config.LEVEL_REPO + for lev in [ + Config.LEVEL_LOCAL, + Config.LEVEL_REPO, + Config.LEVEL_GLOBAL, + Config.LEVEL_SYSTEM, + ]: + self.config.unset( + Config.SECTION_CORE, + Config.SECTION_CORE_REMOTE, + level=lev, + force=True, + ) + if lev == level: + break -def remote_list(config, level=None): - return config.list_options( - config.SECTION_REMOTE_REGEX, config.SECTION_REMOTE_URL, level=level - ) + def modify(self, name, option, value, level=None): + self.config.set( + Config.SECTION_REMOTE_FMT.format(name), option, value, level=level + ) + def list(self, level=None): + return self.config.list_options( + Config.SECTION_REMOTE_REGEX, Config.SECTION_REMOTE_URL, level=level + ) -def remote_default(config, name, unset=False, level=None): - if unset: - config.unset(config.SECTION_CORE, config.SECTION_CORE_REMOTE) - return - config.set( - config.SECTION_CORE, config.SECTION_CORE_REMOTE, name, level=level - ) - - -def set_cache_dir(config, dname, level=None): - configobj = config.get_configobj(level) - path = _resolve_remote_path(dname, configobj.filename) - config.set( - config.SECTION_CACHE, config.SECTION_CACHE_DIR, path, level=level - ) + def set_default(self, name, unset=False, level=None): + if unset: + self.config.unset(Config.SECTION_CORE, Config.SECTION_CORE_REMOTE) + return + self.config.set( + Config.SECTION_CORE, Config.SECTION_CORE_REMOTE, name, level=level + )