diff --git a/dvc/logger.py b/dvc/logger.py index 41b2b6f2e..4685a2b1d 100644 --- a/dvc/logger.py +++ b/dvc/logger.py @@ -221,8 +221,10 @@ class Logger(object): ) for line in lines ] - return "{margin}{padding}{content}{padding}{margin}".format( - margin=cls.colorize(margin, border_color), - padding=''.join(padding_lines), - content=''.join(content_lines), - ) + box = "{margin}{padding}{content}{padding}{margin}".format( + margin=cls.colorize(margin, border_color), + padding=''.join(padding_lines), + content=''.join(content_lines), + ) + + print(box) diff --git a/dvc/updater.py b/dvc/updater.py index c3f42cde6..98748c383 100644 --- a/dvc/updater.py +++ b/dvc/updater.py @@ -1,3 +1,4 @@ +import sys import os import time import requests @@ -17,14 +18,9 @@ class Updater(object): # pragma: no cover def __init__(self, dvc_dir): self.dvc_dir = dvc_dir self.updater_file = os.path.join(dvc_dir, self.UPDATER_FILE) - - @staticmethod - def init(dvc_dir): - return Updater(dvc_dir) + self.current = VERSION_BASE def check(self): - current = VERSION_BASE - if os.getenv('CI'): return @@ -40,36 +36,121 @@ class Updater(object): # pragma: no cover Logger.info('Checking for updates...') try: - r = requests.get(self.URL, timeout=self.TIMEOUT_GET) - j = r.json() - latest = j['version'] - open(self.updater_file, 'w+').close() + self._get_latest_version() except Exception as exc: msg = 'Failed to obtain latest version: {}'.format(str(exc)) Logger.debug(msg) return - l_major, l_minor, l_patch = [int(x) for x in latest.split('.')] - c_major, c_minor, c_patch = [int(x) for x in current.split('.')] + if self._is_outdated(): + self._notify() - if l_major <= c_major and \ - l_minor <= c_minor and \ - l_patch <= c_patch: - return + def _get_latest_version(self): + r = requests.get(self.URL, timeout=self.TIMEOUT_GET) + j = r.json() + self.latest = j['version'] + open(self.updater_file, 'w+').close() - msg = ( + def _is_outdated(self): + l_major, l_minor, l_patch = [int(x) for x in self.latest.split('.')] + c_major, c_minor, c_patch = [int(x) for x in self.current.split('.')] + + return (l_major > c_major or + l_minor > c_minor or + l_patch > c_patch) + + def _notify(self): + message = ( 'Update available {red}{current}{reset} -> {green}{latest}{reset}' - '\n' - 'Run {yellow}pip{reset} install dvc {blue}--upgrade{reset}' - .format( - red=colorama.Fore.RED, - reset=colorama.Fore.RESET, - green=colorama.Fore.GREEN, - yellow=colorama.Fore.YELLOW, - blue=colorama.Fore.BLUE, - current=current, - latest=latest, - ) - ) + + '\n' + + self._get_update_instructions() + ).format(red=colorama.Fore.RED, + reset=colorama.Fore.RESET, + green=colorama.Fore.GREEN, + yellow=colorama.Fore.YELLOW, + blue=colorama.Fore.BLUE, + current=self.current, + latest=self.latest) - Logger.box(msg, border_color='yellow') + if sys.stdout.isatty(): + Logger.box(message, border_color='yellow') + + def _get_update_instructions(self): + instructions = { + 'pip': 'Run {yellow}pip{reset} install dvc {blue}--upgrade{reset}', + 'yum': 'Run {yellow}yum{reset} update dvc', + 'yay': 'Run {yellow}yay{reset} {blue}-S{reset} dvc', + 'formula': 'Run {yellow}brew{reset} upgrade dvc', + 'cask': 'Run {yellow}brew cask{reset} upgrade dvc', + 'apt': ('Run {yellow}apt-get{reset} install' + ' {blue}--only-upgrade{reset} dvc'), + 'binary': ('To upgrade follow this steps:\n' + '1. Uninstall dvc binary\n' + '2. Go to {blue}https://dvc.org{reset}\n' + '3. Download and install new binary'), + None: ('Find the latest release at\n{blue}' + 'https://github.com/iterative/dvc/releases/latest' + '{reset}'), + } + + package_manager = self._get_package_manager() + + return instructions[package_manager] + + @staticmethod + def _is_binary(): + return getattr(sys, 'frozen', False) + + def _get_linux(self): + import distro + + if not self._is_binary(): + return 'pip' + + package_managers = { + 'rhel': 'yum', + 'centos': 'yum', + 'fedora': 'yum', + 'amazon': 'yum', + 'opensuse': 'yum', + 'ubuntu': 'apt', + 'debian': 'apt', + } + + return package_managers.get(distro.id()) + + def _get_darwin(self): + if not self._is_binary(): + if __file__.startswith('/usr/local/Cellar'): + return 'formula' + else: + return 'pip' + + # NOTE: both pkg and cask put dvc binary into /usr/local/bin, + # so in order to know which method of installation was used, + # we need to actually call `brew cask` + ret = os.system('brew cask ls dvc') + if ret == 0: + return 'cask' + + return None + + def _get_windows(self): + return None if self._is_binary() else 'pip' + + def _get_package_manager(self): + import platform + from dvc.exceptions import DvcException + + m = { + 'Windows': self._get_windows, + 'Darwin': self._get_darwin, + 'Linux': self._get_linux, + } + + system = platform.system() + func = m.get(system) + if func is None: + raise DvcException("Not supported system '{}'".format(system)) + + return func() diff --git a/requirements.txt b/requirements.txt index f5fe48990..a21c5e263 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ futures>=3.2.0; python_version == "2.7" grandalf==0.6 pydot>=1.2.4 asciimatics>=1.10.0 +distro>=1.3.0 diff --git a/setup.py b/setup.py index a2612cafb..70e30693f 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ install_requires = [ "requests>=2.18.4", "grandalf==0.6", "asciimatics>=1.10.0", + "distro>=1.3.0", ] # Extra dependencies for remote integrations