diff --git a/dvc/command/config.py b/dvc/command/config.py index 3ef3c2830..aee59cf75 100644 --- a/dvc/command/config.py +++ b/dvc/command/config.py @@ -3,6 +3,7 @@ import logging import os from dvc.command.base import CmdBaseNoRepo, append_doc_link +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -71,7 +72,9 @@ class CmdConfig(CmdBaseNoRepo): prefix = self._config_file_prefix( self.args.show_origin, self.config, level ) - logger.info("\n".join(self._format_config(conf, prefix))) + configs = list(self._format_config(conf, prefix)) + if configs: + ui.write("\n".join(configs)) return 0 @@ -96,7 +99,7 @@ class CmdConfig(CmdBaseNoRepo): prefix = self._config_file_prefix( self.args.show_origin, self.config, level ) - logger.info("{}{}".format(prefix, conf[section][opt])) + ui.write(prefix, conf[section][opt], sep="") break return 0 diff --git a/dvc/command/data_sync.py b/dvc/command/data_sync.py index a7c8c39e4..43c0c7f94 100644 --- a/dvc/command/data_sync.py +++ b/dvc/command/data_sync.py @@ -9,10 +9,11 @@ logger = logging.getLogger(__name__) class CmdDataBase(CmdBase): def log_summary(self, stats): + from dvc.ui import ui from dvc.utils.humanize import get_summary default_msg = "Everything is up to date." - logger.info(get_summary(stats.items()) or default_msg) + ui.write(get_summary(stats.items()) or default_msg) class CmdDataPull(CmdDataBase): diff --git a/dvc/command/destroy.py b/dvc/command/destroy.py index d5f9a4f40..bc0e6bb58 100644 --- a/dvc/command/destroy.py +++ b/dvc/command/destroy.py @@ -1,7 +1,6 @@ import argparse import logging -from dvc import prompt from dvc.command.base import CmdBase, append_doc_link logger = logging.getLogger(__name__) @@ -10,6 +9,7 @@ logger = logging.getLogger(__name__) class CmdDestroy(CmdBase): def run(self): from dvc.exceptions import DvcException + from dvc.ui import ui try: statement = ( @@ -19,7 +19,7 @@ class CmdDestroy(CmdBase): "Are you sure you want to continue?" ) - if not self.args.force and not prompt.confirm(statement): + if not self.args.force and not ui.confirm(statement): raise DvcException( "cannot destroy without a confirmation from the user." " Use `-f` to force." diff --git a/dvc/command/diff.py b/dvc/command/diff.py index 9c8db8201..bc96a38f9 100644 --- a/dvc/command/diff.py +++ b/dvc/command/diff.py @@ -3,10 +3,9 @@ import json import logging import os -import colorama - from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -18,13 +17,12 @@ def _digest(checksum): def _show_md(diff, show_hash=False, hide_missing=False): - from dvc.ui import ui - headers = ["Status", "Hash", "Path"] if show_hash else ["Status", "Path"] rows = [] statuses = ["added", "deleted", "renamed", "modified"] if not hide_missing: statuses.append("not in cache") + for status in statuses: entries = diff.get(status, []) if not entries: @@ -44,7 +42,7 @@ def _show_md(diff, show_hash=False, hide_missing=False): class CmdDiff(CmdBase): @staticmethod - def _format(diff, hide_missing=False): + def _show_diff(diff, hide_missing=False): """ Given a diff structure, generate a string of paths separated by new lines and grouped together by their state. @@ -71,15 +69,14 @@ class CmdDiff(CmdBase): """ colors = { - "added": colorama.Fore.GREEN, - "modified": colorama.Fore.YELLOW, - "deleted": colorama.Fore.RED, - "renamed": colorama.Fore.GREEN, - "not in cache": colorama.Fore.YELLOW, + "added": "green", + "modified": "yellow", + "deleted": "red", + "renamed": "green", + "not in cache": "yellow", } summary = {} - groups = [] states = ["added", "deleted", "renamed", "modified"] if not hide_missing: @@ -91,7 +88,8 @@ class CmdDiff(CmdBase): if not entries: continue - content = [] + header = state.capitalize() + ui.write(f"[{colors[state]}]{header}[/]:", styled=True) for entry in entries: path = entry["path"] @@ -99,7 +97,7 @@ class CmdDiff(CmdBase): path = f"{path['old']} -> {path['new']}" checksum = entry.get("hash") summary[state] += 1 if not path.endswith(os.sep) else 0 - content.append( + ui.write( "{space}{checksum}{separator}{path}".format( space=" ", checksum=_digest(checksum) if checksum else "", @@ -108,14 +106,7 @@ class CmdDiff(CmdBase): ) ) - groups.append( - "{color}{header}{nc}:\n{content}".format( - color=colors[state], - header=state.capitalize(), - nc=colorama.Fore.RESET, - content="\n".join(content), - ) - ) + ui.write() if not sum(summary.values()): return None @@ -126,9 +117,8 @@ class CmdDiff(CmdBase): ) if not hide_missing: fmt += ", {not in cache} not in cache" - groups.append(fmt.format_map(summary)) - return "\n\n".join(groups) + ui.write(fmt.format_map(summary)) def run(self): from dvc.exceptions import DvcException @@ -155,13 +145,11 @@ class CmdDiff(CmdBase): diff[key] = entries if self.args.show_json: - logger.info(json.dumps(diff)) + ui.write(json.dumps(diff)) elif self.args.show_md: - logger.info(_show_md(diff, show_hash, hide_missing)) + _show_md(diff, show_hash, hide_missing) elif diff: - output = self._format(diff, hide_missing) - if output: - logger.info(output) + self._show_diff(diff, hide_missing) except DvcException: logger.exception("failed to get diff") diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py index 3d4613ab9..2c96cd769 100644 --- a/dvc/command/experiments.py +++ b/dvc/command/experiments.py @@ -14,6 +14,7 @@ from dvc.command.metrics import DEFAULT_PRECISION from dvc.command.repro import CmdRepro from dvc.command.repro import add_arguments as add_repro_arguments from dvc.exceptions import DvcException, InvalidArgumentError +from dvc.ui import ui from dvc.utils.flatten import flatten if TYPE_CHECKING: @@ -450,7 +451,7 @@ class CmdExperimentsShow(CmdBase): if self.args.show_json: import json - logger.info(json.dumps(all_experiments, default=_format_json)) + ui.write(json.dumps(all_experiments, default=_format_json)) else: show_experiments( all_experiments, @@ -494,7 +495,7 @@ class CmdExperimentsDiff(CmdBase): if self.args.show_json: import json - logger.info(json.dumps(diff)) + ui.write(json.dumps(diff)) else: from dvc.compare import show_diff @@ -502,8 +503,6 @@ class CmdExperimentsDiff(CmdBase): diffs = [("metrics", "Metric"), ("params", "Param")] for idx, (key, title) in enumerate(diffs): if idx: - from dvc.ui import ui - # we are printing tables even in `--quiet` mode # so we should also be printing the "table" separator ui.write(force=True) @@ -537,7 +536,7 @@ class CmdExperimentsRun(CmdRepro): ) if self.args.reset: - logger.info("Any existing checkpoints will be reset and re-run.") + ui.write("Any existing checkpoints will be reset and re-run.") results = self.repo.experiments.run( name=self.args.name, @@ -554,7 +553,7 @@ class CmdExperimentsRun(CmdRepro): if self.args.metrics and results: metrics = self.repo.metrics.show(revs=list(results)) metrics.pop("workspace", None) - logger.info(show_metrics(metrics)) + show_metrics(metrics) return 0 @@ -608,12 +607,13 @@ class CmdExperimentsGC(CmdRepro): ) if removed: - logger.info( - f"Removed {removed} experiments. To remove unused cache files " - "use 'dvc gc'." + ui.write( + f"Removed {removed} experiments.", + "To remove unused cache files", + "use 'dvc gc'.", ) else: - logger.info("No experiments to remove.") + ui.write("No experiments to remove.") return 0 @@ -662,15 +662,15 @@ class CmdExperimentsPush(CmdBase): run_cache=self.args.run_cache, ) - logger.info( - "Pushed experiment '%s' to Git remote '%s'.", - self.args.experiment, - self.args.git_remote, + ui.write( + f"Pushed experiment '{self.args.experiment}'" + f"to Git remote '{self.args.git_remote}'." ) if not self.args.push_cache: - logger.info( - "To push cached outputs for this experiment to DVC remote " - "storage, re-run this command without '--no-cache'." + ui.write( + "To push cached outputs", + "for this experiment to DVC remote storage," + "re-run this command without '--no-cache'.", ) return 0 @@ -689,15 +689,15 @@ class CmdExperimentsPull(CmdBase): run_cache=self.args.run_cache, ) - logger.info( - "Pulled experiment '%s' from Git remote '%s'. ", - self.args.experiment, - self.args.git_remote, + ui.write( + f"Pulled experiment '{self.args.experiment}'", + f"from Git remote '{self.args.git_remote}'.", ) if not self.args.pull_cache: - logger.info( - "To pull cached outputs for this experiment from DVC remote " - "storage, re-run this command without '--no-cache'." + ui.write( + "To pull cached outputs for this experiment" + "from DVC remote storage," + "re-run this command without '--no-cache'." ) return 0 diff --git a/dvc/command/gc.py b/dvc/command/gc.py index 59af7385a..ef7305bb9 100644 --- a/dvc/command/gc.py +++ b/dvc/command/gc.py @@ -2,8 +2,8 @@ import argparse import logging import os -from dvc import prompt from dvc.command.base import CmdBase, append_doc_link +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -44,7 +44,7 @@ class CmdGC(CmdBase): logger.warning(msg) msg = "Are you sure you want to proceed?" - if not self.args.force and not prompt.confirm(msg): + if not self.args.force and not ui.confirm(msg): return 1 self.repo.gc( diff --git a/dvc/command/get.py b/dvc/command/get.py index 15f70f4a1..f6e617b3f 100644 --- a/dvc/command/get.py +++ b/dvc/command/get.py @@ -12,12 +12,13 @@ logger = logging.getLogger(__name__) class CmdGet(CmdBaseNoRepo): def _show_url(self): from dvc.api import get_url + from dvc.ui import ui try: url = get_url( self.args.path, repo=self.args.url, rev=self.args.rev ) - logger.info(url) + ui.write(url, force=True) except DvcException: logger.exception("failed to show URL") return 1 diff --git a/dvc/command/init.py b/dvc/command/init.py index a583a62cd..a1abf3267 100644 --- a/dvc/command/init.py +++ b/dvc/command/init.py @@ -12,8 +12,10 @@ logger = logging.getLogger(__name__) def _welcome_message(): + from dvc.ui import ui + if analytics.is_enabled(): - logger.info( + ui.write( boxify( "DVC has enabled anonymous aggregate usage analytics.\n" "Read the analytics documentation (and how to opt-out) here:\n" @@ -30,7 +32,7 @@ def _welcome_message(): f"- Star us on GitHub: {fmt_link('https://github.com/iterative/dvc')}" ).format(yellow=colorama.Fore.YELLOW, nc=colorama.Fore.RESET) - logger.info(msg) + ui.write(msg) class CmdInit(CmdBaseNoRepo): diff --git a/dvc/command/live.py b/dvc/command/live.py index 1eb753457..b0959663a 100644 --- a/dvc/command/live.py +++ b/dvc/command/live.py @@ -1,11 +1,9 @@ import argparse -import logging import os from dvc.command import completion from dvc.command.base import CmdBase, fix_subparsers - -logger = logging.getLogger(__name__) +from dvc.ui import ui class CmdLive(CmdBase): @@ -17,7 +15,7 @@ class CmdLive(CmdBase): html_path = self.args.target + ".html" self.repo.plots.write_html(html_path, plots, metrics) - logger.info(f"\nfile://{os.path.abspath(html_path)}") + ui.write("\nfile://", os.path.abspath(html_path), sep="") return 0 diff --git a/dvc/command/metrics.py b/dvc/command/metrics.py index 35535ed9f..0823ff4e6 100644 --- a/dvc/command/metrics.py +++ b/dvc/command/metrics.py @@ -4,6 +4,7 @@ import logging from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link, fix_subparsers from dvc.exceptions import BadMetricError, DvcException +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -32,7 +33,7 @@ class CmdMetricsShow(CmdMetricsBase): if self.args.show_json: import json - logger.info(json.dumps(metrics)) + ui.write(json.dumps(metrics)) else: from dvc.compare import show_metrics @@ -73,7 +74,7 @@ class CmdMetricsDiff(CmdMetricsBase): if self.args.show_json: import json - logger.info(json.dumps(diff)) + ui.write(json.dumps(diff)) else: from dvc.compare import show_diff diff --git a/dvc/command/params.py b/dvc/command/params.py index a4d317ea7..4f87d00fb 100644 --- a/dvc/command/params.py +++ b/dvc/command/params.py @@ -4,6 +4,7 @@ import logging from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link, fix_subparsers from dvc.exceptions import DvcException +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -27,7 +28,7 @@ class CmdParamsDiff(CmdBase): if self.args.show_json: import json - logger.info(json.dumps(diff)) + ui.write(json.dumps(diff)) else: from dvc.compare import show_diff diff --git a/dvc/command/plots.py b/dvc/command/plots.py index cc0f331ae..3904f61fd 100644 --- a/dvc/command/plots.py +++ b/dvc/command/plots.py @@ -4,6 +4,7 @@ import logging from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link, fix_subparsers from dvc.exceptions import DvcException +from dvc.ui import ui from dvc.utils import format_link logger = logging.getLogger(__name__) @@ -38,7 +39,7 @@ class CmdPlots(CmdBase): if self.args.show_vega: target = self.args.targets[0] - logger.info(plots[target]) + ui.write(plots[target]) return 0 rel: str = self.args.out or "plots.html" @@ -53,13 +54,15 @@ class CmdPlots(CmdBase): assert path.is_absolute() # as_uri throws ValueError if not absolute url = path.as_uri() - logger.info(url) + ui.write(url) if self.args.open: import webbrowser opened = webbrowser.open(rel) if not opened: - logger.error("Failed to open. Please try opening it manually.") + ui.error_write( + "Failed to open. Please try opening it manually." + ) return 1 return 0 diff --git a/dvc/command/remote.py b/dvc/command/remote.py index 06ca0200d..cb9121888 100644 --- a/dvc/command/remote.py +++ b/dvc/command/remote.py @@ -1,12 +1,10 @@ import argparse -import logging from dvc.command.base import append_doc_link, fix_subparsers from dvc.command.config import CmdConfig +from dvc.ui import ui from dvc.utils import format_link -logger = logging.getLogger(__name__) - class CmdRemote(CmdConfig): def __init__(self, args): @@ -27,7 +25,7 @@ class CmdRemoteAdd(CmdRemote): from dvc.config import ConfigError if self.args.default: - logger.info(f"Setting '{self.args.name}' as a default remote.") + ui.write(f"Setting '{self.args.name}' as a default remote.") with self.config.edit(self.args.level) as conf: if self.args.name in conf["remote"] and not self.args.force: @@ -90,7 +88,7 @@ class CmdRemoteDefault(CmdRemote): try: print(conf["core"]["remote"]) except KeyError: - logger.info("No default remote set") + ui.write("No default remote set") return 1 else: with self.config.edit(self.args.level) as conf: @@ -116,7 +114,7 @@ class CmdRemoteList(CmdRemote): def run(self): conf = self.config.read(self.args.level) for name, conf in conf["remote"].items(): - logger.info("{}\t{}".format(name, conf["url"])) + ui.write(name, conf["url"], sep="\t") return 0 diff --git a/dvc/command/repro.py b/dvc/command/repro.py index 3d7193ba9..fd9034fbf 100644 --- a/dvc/command/repro.py +++ b/dvc/command/repro.py @@ -1,20 +1,19 @@ import argparse -import logging from dvc.command import completion from dvc.command.base import CmdBase, append_doc_link from dvc.command.status import CmdDataStatus -logger = logging.getLogger(__name__) - class CmdRepro(CmdBase): def run(self): + from dvc.ui import ui + stages = self.repo.reproduce(**self._repro_kwargs) if len(stages) == 0: - logger.info(CmdDataStatus.UP_TO_DATE_MSG) + ui.write(CmdDataStatus.UP_TO_DATE_MSG) else: - logger.info( + ui.write( "Use `dvc push` to send your updates to " "remote storage." ) diff --git a/dvc/command/root.py b/dvc/command/root.py index bbcb8fc82..08b3a4154 100644 --- a/dvc/command/root.py +++ b/dvc/command/root.py @@ -10,8 +10,9 @@ logger = logging.getLogger(__name__) class CmdRoot(CmdBaseNoRepo): def run(self): from dvc.repo import Repo + from dvc.ui import ui - logger.info(relpath(Repo.find_root())) + ui.write(relpath(Repo.find_root())) return 0 diff --git a/dvc/command/status.py b/dvc/command/status.py index 741c59710..7a37c5a63 100644 --- a/dvc/command/status.py +++ b/dvc/command/status.py @@ -2,6 +2,7 @@ import logging from dvc.command.data_sync import CmdDataBase from dvc.exceptions import DvcException +from dvc.ui import ui from dvc.utils import format_link logger = logging.getLogger(__name__) @@ -26,7 +27,7 @@ class CmdDataStatus(CmdDataBase): ind = indent * self.STATUS_INDENT if isinstance(status, str): - logger.info(f"{ind}{status}") + ui.write(f"{ind}{status}") return if isinstance(status, list): @@ -38,14 +39,13 @@ class CmdDataStatus(CmdDataBase): for key, value in status.items(): if isinstance(value, str): - logger.info("{}{}{}".format(ind, self._normalize(value), key)) + ui.write("{}{}{}".format(ind, self._normalize(value), key)) elif value: - logger.info(f"{ind}{key}:") + ui.write(f"{ind}{key}:") self._show(value, indent + 1) def run(self): from dvc.repo import lock_repo - from dvc.ui import ui indent = 1 if self.args.cloud else 0 @@ -72,7 +72,7 @@ class CmdDataStatus(CmdDataBase): if self.args.show_json: import json - logger.info(json.dumps(st)) + ui.write(json.dumps(st)) return 0 if st: diff --git a/dvc/command/version.py b/dvc/command/version.py index c2db8f31a..1154e380d 100644 --- a/dvc/command/version.py +++ b/dvc/command/version.py @@ -2,6 +2,7 @@ import argparse import logging from dvc.command.base import CmdBaseNoRepo, append_doc_link +from dvc.ui import ui logger = logging.getLogger(__name__) @@ -11,7 +12,7 @@ class CmdVersion(CmdBaseNoRepo): from dvc.info import get_dvc_info dvc_info = get_dvc_info() - logger.info(dvc_info) + ui.write(dvc_info, force=True) return 0 diff --git a/dvc/ui/__init__.py b/dvc/ui/__init__.py index 3a06ae0ef..71d42e798 100644 --- a/dvc/ui/__init__.py +++ b/dvc/ui/__init__.py @@ -1,5 +1,4 @@ import sys -from collections import defaultdict from typing import ( TYPE_CHECKING, Any, @@ -7,20 +6,21 @@ from typing import ( Iterable, Optional, Sequence, + TextIO, Union, ) from funcy import cached_property -from dvc.progress import Tqdm -from dvc.utils import colorize - if TYPE_CHECKING: + from dvc.progress import Tqdm from dvc.ui.table import Headers, Styles, TableData class Formatter: def __init__(self, theme: Dict = None, defaults: Dict = None) -> None: + from collections import defaultdict + theme = theme or { "success": {"color": "green", "style": "bold"}, "warn": {"color": "yellow"}, @@ -29,6 +29,8 @@ class Formatter: self.theme = defaultdict(lambda: defaults or {}, theme) def format(self, message: str, style: str = None, **kwargs) -> str: + from dvc.utils import colorize + return colorize(message, **self.theme[style]) @@ -75,6 +77,7 @@ class Console: stderr: bool = False, force: bool = False, styled: bool = False, + file: TextIO = None, ) -> None: sep = " " if sep is None else sep end = "\n" if end is None else end @@ -85,12 +88,14 @@ class Console: console = self.error_console if stderr else self.rich_console return console.print(*objects, sep=sep, end=end) - file = sys.stderr if stderr else sys.stdout + file = file or (sys.stderr if stderr else sys.stdout) values = (self.formatter.format(obj, style=style) for obj in objects) return print(*values, sep=sep, end=end, file=file) @staticmethod - def progress(*args, **kwargs) -> Tqdm: + def progress(*args, **kwargs) -> "Tqdm": + from dvc.progress import Tqdm + return Tqdm(*args, **kwargs) def prompt( diff --git a/tests/func/plots/test_show.py b/tests/func/plots/test_show.py index 326a43ef9..221e3fecf 100644 --- a/tests/func/plots/test_show.py +++ b/tests/func/plots/test_show.py @@ -683,17 +683,18 @@ def test_show_no_repo(tmp_dir): dvc.plots.show(["metric.json"]) -def test_show_from_subdir(tmp_dir, dvc, caplog): +def test_show_from_subdir(tmp_dir, dvc, capsys): subdir = tmp_dir / "subdir" subdir.mkdir() metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}] _write_json(subdir, metric, "metric.json") - with subdir.chdir(), caplog.at_level(logging.INFO, "dvc"): + with subdir.chdir(): assert main(["plots", "show", "metric.json"]) == 0 - assert subdir.as_uri() in caplog.text + out, _ = capsys.readouterr() + assert subdir.as_uri() in out assert (subdir / "plots.html").exists() diff --git a/tests/func/test_config.py b/tests/func/test_config.py index ec2a02af8..9dbf9c86c 100644 --- a/tests/func/test_config.py +++ b/tests/func/test_config.py @@ -98,7 +98,7 @@ def test_config_set_local(tmp_dir, dvc): ), ], ) -def test_config_get(tmp_dir, dvc, caplog, args, ret, msg): +def test_config_get(tmp_dir, dvc, capsys, caplog, args, ret, msg): (tmp_dir / ".dvc" / "config").write_text( textwrap.dedent( """\ @@ -123,12 +123,12 @@ def test_config_get(tmp_dir, dvc, caplog, args, ret, msg): ) ) - caplog.clear() assert main(["config"] + args) == ret - assert msg in caplog.text + text = caplog.text if ret else capsys.readouterr()[0] + assert msg in text -def test_config_list(tmp_dir, dvc, caplog): +def test_config_list(tmp_dir, dvc, capsys): (tmp_dir / ".dvc" / "config").write_text( textwrap.dedent( """\ @@ -155,17 +155,18 @@ def test_config_list(tmp_dir, dvc, caplog): ) ) - caplog.clear() assert main(["config", "--list"]) == 0 - assert "remote.myremote.url=s3://bucket/path" in caplog.text - assert "remote.myremote.region=us-east-2" in caplog.text - assert "remote.myremote.profile=iterative" in caplog.text - assert "remote.myremote.access_key_id=abcde" in caplog.text - assert "remote.myremote.secret_access_key=123456" in caplog.text - assert "remote.other.url=gs://bucket/path" in caplog.text - assert "core.analytics=False" in caplog.text - assert "core.no_scm=true" in caplog.text - assert "core.remote=myremote" in caplog.text + + out, _ = capsys.readouterr() + assert "remote.myremote.url=s3://bucket/path" in out + assert "remote.myremote.region=us-east-2" in out + assert "remote.myremote.profile=iterative" in out + assert "remote.myremote.access_key_id=abcde" in out + assert "remote.myremote.secret_access_key=123456" in out + assert "remote.other.url=gs://bucket/path" in out + assert "core.analytics=False" in out + assert "core.no_scm=true" in out + assert "core.remote=myremote" in out @pytest.mark.parametrize( @@ -241,57 +242,56 @@ def test_load_relative_paths(dvc, field, remote_url): ) -def test_config_remote(tmp_dir, dvc, caplog): +def test_config_remote(tmp_dir, dvc, capsys): (tmp_dir / ".dvc" / "config").write_text( "['remote \"myremote\"']\n" " url = s3://bucket/path\n" " region = myregion\n" ) - caplog.clear() assert main(["config", "remote.myremote.url"]) == 0 - assert "s3://bucket/path" in caplog.text + out, _ = capsys.readouterr() + assert "s3://bucket/path" in out - caplog.clear() assert main(["config", "remote.myremote.region"]) == 0 - assert "myregion" in caplog.text + out, _ = capsys.readouterr() + assert "myregion" in out -def test_config_show_origin_single(tmp_dir, dvc, caplog): +def test_config_show_origin_single(tmp_dir, dvc, capsys): (tmp_dir / ".dvc" / "config").write_text( "['remote \"myremote\"']\n" " url = s3://bucket/path\n" " region = myregion\n" ) - caplog.clear() assert ( main(["config", "--show-origin", "--project", "remote.myremote.url"]) == 0 ) + out, _ = capsys.readouterr() assert ( "{}\t{}\n".format(os.path.join(".dvc", "config"), "s3://bucket/path") - in caplog.text + in out ) - caplog.clear() assert ( main(["config", "--show-origin", "--local", "remote.myremote.url"]) == 251 ) - caplog.clear() assert main(["config", "--list", "--project", "--show-origin"]) == 0 + out, _ = capsys.readouterr() assert ( "{}\t{}\n".format( os.path.join(".dvc", "config"), "remote.myremote.url=s3://bucket/path", ) - in caplog.text + in out ) -def test_config_show_origin_merged(tmp_dir, dvc, caplog): +def test_config_show_origin_merged(tmp_dir, dvc, capsys): (tmp_dir / ".dvc" / "config").write_text( "['remote \"myremote\"']\n" " url = s3://bucket/path\n" @@ -302,18 +302,19 @@ def test_config_show_origin_merged(tmp_dir, dvc, caplog): "['remote \"myremote\"']\n timeout = 100\n" ) - caplog.clear() assert main(["config", "--list", "--show-origin"]) == 0 + out, _ = capsys.readouterr() assert ( "{}\t{}\n".format( os.path.join(".dvc", "config"), "remote.myremote.url=s3://bucket/path", ) - in caplog.text + in out ) + assert ( "{}\t{}\n".format( os.path.join(".dvc", "config.local"), "remote.myremote.timeout=100" ) - in caplog.text + in out ) diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py index cec4d3fcd..b4eeb3f59 100644 --- a/tests/func/test_data_cloud.py +++ b/tests/func/test_data_cloud.py @@ -475,13 +475,12 @@ def test_pipeline_file_target_ops(tmp_dir, dvc, run_copy, local_remote): ({}, "Everything is up to date"), ], ) -def test_push_stats(tmp_dir, dvc, fs, msg, caplog, local_remote): +def test_push_stats(tmp_dir, dvc, fs, msg, capsys, local_remote): tmp_dir.dvc_gen(fs) - caplog.clear() - with caplog.at_level(level=logging.INFO, logger="dvc"): - main(["push"]) - assert msg in caplog.text + main(["push"]) + out, _ = capsys.readouterr() + assert msg in out @pytest.mark.parametrize( @@ -492,17 +491,17 @@ def test_push_stats(tmp_dir, dvc, fs, msg, caplog, local_remote): ({}, "Everything is up to date."), ], ) -def test_fetch_stats(tmp_dir, dvc, fs, msg, caplog, local_remote): +def test_fetch_stats(tmp_dir, dvc, fs, msg, capsys, local_remote): tmp_dir.dvc_gen(fs) dvc.push() clean(list(fs.keys()), dvc) - caplog.clear() - with caplog.at_level(level=logging.INFO, logger="dvc"): - main(["fetch"]) - assert msg in caplog.text + + main(["fetch"]) + out, _ = capsys.readouterr() + assert msg in out -def test_pull_stats(tmp_dir, dvc, capsys, caplog, local_remote): +def test_pull_stats(tmp_dir, dvc, capsys, local_remote): tmp_dir.dvc_gen({"foo": "foo", "bar": "bar"}) dvc.push() clean(["foo", "bar"], dvc) @@ -513,12 +512,13 @@ def test_pull_stats(tmp_dir, dvc, capsys, caplog, local_remote): out, _ = capsys.readouterr() assert "M\tbar".expandtabs() in out assert "A\tfoo".expandtabs() in out - assert "2 files fetched" in caplog.text - assert "1 file added" in caplog.text - assert "1 file modified" in caplog.text - with caplog.at_level(level=logging.INFO, logger="dvc"): - main(["pull"]) - assert "Everything is up to date." in caplog.text + assert "2 files fetched" in out + assert "1 file added" in out + assert "1 file modified" in out + + main(["pull"]) + out, _ = capsys.readouterr() + assert "Everything is up to date." in out @pytest.mark.parametrize( diff --git a/tests/func/test_root.py b/tests/func/test_root.py index f39562c5f..6465f1329 100644 --- a/tests/func/test_root.py +++ b/tests/func/test_root.py @@ -1,13 +1,13 @@ from dvc.main import main -def test_root(tmp_dir, dvc, caplog): +def test_root(tmp_dir, dvc, capsys): assert main(["root"]) == 0 - assert ".\n" in caplog.text + assert "." in capsys.readouterr()[0] -def test_root_locked(tmp_dir, dvc, caplog): +def test_root_locked(tmp_dir, dvc, capsys): # NOTE: check that `dvc root` is not blocked with dvc lock with dvc.lock: assert main(["root"]) == 0 - assert ".\n" in caplog.text + assert "." in capsys.readouterr()[0] diff --git a/tests/func/test_version.py b/tests/func/test_version.py index 6b421cfe1..e08f05ce8 100644 --- a/tests/func/test_version.py +++ b/tests/func/test_version.py @@ -4,13 +4,14 @@ from dvc.main import main from tests.unit.test_info import PYTHON_VERSION_REGEX -def test_(tmp_dir, dvc, scm, caplog): +def test_(tmp_dir, dvc, scm, capsys): assert main(["version"]) == 0 - assert re.search(r"DVC version: \d+\.\d+\.\d+.*", caplog.text) - assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", caplog.text) - assert re.search(r"Supports: .*", caplog.text) - assert re.search(r"Cache types: .*", caplog.text) - assert re.search(r"Caches: local", caplog.text) - assert re.search(r"Remotes: None", caplog.text) - assert "Repo: dvc, git" in caplog.text + out, _ = capsys.readouterr() + assert re.search(r"DVC version: \d+\.\d+\.\d+.*", out) + assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", out) + assert re.search(r"Supports: .*", out) + assert re.search(r"Cache types: .*", out) + assert re.search(r"Caches: local", out) + assert re.search(r"Remotes: None", out) + assert "Repo: dvc, git" in out diff --git a/tests/unit/command/test_diff.py b/tests/unit/command/test_diff.py index b7473542c..023ceccb2 100644 --- a/tests/unit/command/test_diff.py +++ b/tests/unit/command/test_diff.py @@ -1,5 +1,4 @@ import collections -import logging import os import mock @@ -21,7 +20,7 @@ def test_digest(checksum, expected): assert expected == _digest(checksum) -def test_default(mocker, caplog): +def test_default(mocker, capsys): args = parse_args(["diff"]) cmd = args.func(args) diff = { @@ -51,10 +50,10 @@ def test_default(mocker, caplog): "\n" "files summary: 1 added, 0 deleted, " "1 renamed, 0 modified, 0 not in cache" - ).format(sep=os.path.sep) in caplog.text + ).format(sep=os.path.sep) in capsys.readouterr()[0] -def test_show_hash(mocker, caplog): +def test_show_hash(mocker, capsys): args = parse_args(["diff", "--show-hash"]) cmd = args.func(args) diff = { @@ -81,6 +80,8 @@ def test_show_hash(mocker, caplog): } mocker.patch("dvc.repo.Repo.diff", return_value=diff) assert 0 == cmd.run() + + out, _ = capsys.readouterr() assert ( "Deleted:\n" " XXXXXXXX " + os.path.join("data", "") + "\n" @@ -100,10 +101,10 @@ def test_show_hash(mocker, caplog): "\n" "files summary: 0 added, 2 deleted, " "1 renamed, 2 modified, 0 not in cache" - ) in caplog.text + ) in out -def test_show_json(mocker, caplog): +def test_show_json(mocker, capsys): args = parse_args(["diff", "--show-json"]) cmd = args.func(args) diff = { @@ -118,13 +119,14 @@ def test_show_json(mocker, caplog): mocker.patch("dvc.repo.Repo.diff", return_value=diff) assert 0 == cmd.run() - assert '"added": [{"path": "file1"}, {"path": "file2"}]' in caplog.text - assert '"deleted": []' in caplog.text - assert '"modified": []' in caplog.text - assert '"not in cache": []' in caplog.text + out, _ = capsys.readouterr() + assert '"added": [{"path": "file1"}, {"path": "file2"}]' in out + assert '"deleted": []' in out + assert '"modified": []' in out + assert '"not in cache": []' in out -def test_show_json_and_hash(mocker, caplog): +def test_show_json_and_hash(mocker, capsys): args = parse_args(["diff", "--show-json", "--show-hash"]) cmd = args.func(args) @@ -147,20 +149,21 @@ def test_show_json_and_hash(mocker, caplog): mocker.patch("dvc.repo.Repo.diff", return_value=diff) assert 0 == cmd.run() + out, _ = capsys.readouterr() assert ( '"added": [{"path": "file1", "hash": "11111111"}, ' - '{"path": "file2", "hash": "22222222"}]' in caplog.text + '{"path": "file2", "hash": "22222222"}]' in out ) - assert '"deleted": []' in caplog.text - assert '"modified": []' in caplog.text + assert '"deleted": []' in out + assert '"modified": []' in out assert ( '"renamed": [{"path": {"old": "file_old", ' - '"new": "file_new"}, "hash": "11111111"}]' in caplog.text + '"new": "file_new"}, "hash": "11111111"}]' in out ) - assert '"not in cache": []' in caplog.text + assert '"not in cache": []' in out -def test_show_json_hide_missing(mocker, caplog): +def test_show_json_hide_missing(mocker, capsys): args = parse_args(["diff", "--show-json", "--hide-missing"]) cmd = args.func(args) diff = { @@ -181,19 +184,17 @@ def test_show_json_hide_missing(mocker, caplog): mocker.patch("dvc.repo.Repo.diff", return_value=diff) assert 0 == cmd.run() - assert '"added": [{"path": "file1"}, {"path": "file2"}]' in caplog.text - assert '"deleted": []' in caplog.text - assert ( - '"renamed": [{"path": {"old": "file_old", "new": "file_new"}' - in caplog.text - ) - assert '"modified": []' in caplog.text - assert '"not in cache": []' not in caplog.text + out, _ = capsys.readouterr() + assert '"added": [{"path": "file1"}, {"path": "file2"}]' in out + assert '"deleted": []' in out + assert '"renamed": [{"path": {"old": "file_old", "new": "file_new"}' in out + assert '"modified": []' in out + assert '"not in cache": []' not in out @pytest.mark.parametrize("show_hash", [None, True, False]) @mock.patch("dvc.command.diff._show_md") -def test_diff_show_md_and_hash(mock_show_md, mocker, caplog, show_hash): +def test_diff_show_md_and_hash(mock_show_md, mocker, show_hash): options = ["diff", "--show-md"] + (["--show-hash"] if show_hash else []) args = parse_args(options) cmd = args.func(args) @@ -206,27 +207,20 @@ def test_diff_show_md_and_hash(mock_show_md, mocker, caplog, show_hash): mock_show_md.assert_called_once_with(diff, show_hash, False) -def test_no_changes(mocker, caplog): +def test_no_changes(mocker, capsys): args = parse_args(["diff", "--show-json"]) cmd = args.func(args) mocker.patch("dvc.repo.Repo.diff", return_value={}) - def info(): - return [ - msg - for name, level, msg in caplog.record_tuples - if name.startswith("dvc") and level == logging.INFO - ] - assert 0 == cmd.run() - assert ["{}"] == info() - - caplog.clear() + out, _ = capsys.readouterr() + assert "{}" in out args = parse_args(["diff"]) cmd = args.func(args) assert 0 == cmd.run() - assert not info() + out, _ = capsys.readouterr() + assert not out def test_show_md(capsys): @@ -330,7 +324,7 @@ def test_show_md_hide_missing(capsys): ).format(sep=os.path.sep) -def test_hide_missing(mocker, caplog): +def test_hide_missing(mocker, capsys): args = parse_args(["diff", "--hide-missing"]) cmd = args.func(args) diff = { @@ -348,6 +342,7 @@ def test_hide_missing(mocker, caplog): mocker.patch("dvc.repo.Repo.diff", return_value=diff) assert 0 == cmd.run() + out, _ = capsys.readouterr() assert ( "Added:\n" " file\n" @@ -356,5 +351,5 @@ def test_hide_missing(mocker, caplog): " file_old -> file_new\n" "\n" "files summary: 1 added, 0 deleted, 1 renamed, 0 modified" - ) in caplog.text - assert "not in cache" not in caplog.text + ) in out + assert "not in cache" not in out diff --git a/tests/unit/command/test_get.py b/tests/unit/command/test_get.py index db76ee2b0..0df39a6e2 100644 --- a/tests/unit/command/test_get.py +++ b/tests/unit/command/test_get.py @@ -28,7 +28,7 @@ def test_get(mocker): ) -def test_get_url(mocker, caplog): +def test_get_url(mocker, capsys): cli_args = parse_args( ["get", "repo_url", "src", "--rev", "version", "--show-url"] ) @@ -38,6 +38,7 @@ def test_get_url(mocker, caplog): m = mocker.patch("dvc.api.get_url", return_value="resource_url") assert cmd.run() == 0 - assert "resource_url" in caplog.text + out, _ = capsys.readouterr() + assert "resource_url" in out m.assert_called_once_with("src", repo="repo_url", rev="version") diff --git a/tests/unit/command/test_metrics.py b/tests/unit/command/test_metrics.py index db94864f7..4b233abe8 100644 --- a/tests/unit/command/test_metrics.py +++ b/tests/unit/command/test_metrics.py @@ -50,7 +50,7 @@ def test_metrics_diff(dvc, mocker, capsys): ) -def test_metrics_diff_json(dvc, mocker, caplog, capsys): +def test_metrics_diff_json(dvc, mocker, capsys): cli_args = parse_args( [ "metrics", @@ -79,6 +79,7 @@ def test_metrics_diff_json(dvc, mocker, caplog, capsys): show_diff_mock = mocker.patch("dvc.compare.show_diff") assert cmd.run() == 0 + out, _ = capsys.readouterr() metrics_diff.assert_called_once_with( cmd.repo, targets=["target1", "target2"], @@ -88,8 +89,7 @@ def test_metrics_diff_json(dvc, mocker, caplog, capsys): all=True, ) show_diff_mock.assert_not_called() - assert json.dumps(diff) in caplog.text - assert capsys.readouterr() == ("", "") + assert json.dumps(diff) in out def test_metrics_show(dvc, mocker): @@ -134,7 +134,7 @@ def test_metrics_show(dvc, mocker): ) -def test_metrics_show_json(dvc, mocker, caplog, capsys): +def test_metrics_show_json(dvc, mocker, capsys): cli_args = parse_args( [ "metrics", @@ -161,7 +161,7 @@ def test_metrics_show_json(dvc, mocker, caplog, capsys): show_metrics_mock = mocker.patch("dvc.compare.show_metrics") assert cmd.run() == 0 - + out, _ = capsys.readouterr() metrics_show.assert_called_once_with( cmd.repo, ["target1", "target2"], @@ -171,5 +171,4 @@ def test_metrics_show_json(dvc, mocker, caplog, capsys): all_commits=True, ) show_metrics_mock.assert_not_called() - assert json.dumps(d) in caplog.text - assert capsys.readouterr() == ("", "") + assert json.dumps(d) in out diff --git a/tests/unit/command/test_params.py b/tests/unit/command/test_params.py index 87fd819a9..41eb04b15 100644 --- a/tests/unit/command/test_params.py +++ b/tests/unit/command/test_params.py @@ -1,5 +1,3 @@ -import logging - from dvc.cli import parse_args from dvc.command.params import CmdParamsDiff @@ -57,7 +55,7 @@ def test_params_diff_from_cli(dvc, mocker): ) -def test_params_diff_show_json(dvc, mocker, caplog, capsys): +def test_params_diff_show_json(dvc, mocker, capsys): cli_args = parse_args( ["params", "diff", "HEAD~10", "HEAD~1", "--show-json"] ) @@ -67,9 +65,7 @@ def test_params_diff_show_json(dvc, mocker, caplog, capsys): ) show_diff_mock = mocker.patch("dvc.compare.show_diff") - with caplog.at_level(logging.INFO, logger="dvc"): - assert cmd.run() == 0 - assert '{"params.yaml": {"a": "b"}}\n' in caplog.text - + assert cmd.run() == 0 + out, _ = capsys.readouterr() + assert '{"params.yaml": {"a": "b"}}\n' in out show_diff_mock.assert_not_called() - assert capsys.readouterr() == ("", "") diff --git a/tests/unit/command/test_plots.py b/tests/unit/command/test_plots.py index 6a88de8d2..1102e477c 100644 --- a/tests/unit/command/test_plots.py +++ b/tests/unit/command/test_plots.py @@ -1,4 +1,3 @@ -import logging import os import posixpath @@ -91,7 +90,7 @@ def test_plots_show_vega(dvc, mocker): ) -def test_plots_diff_vega(dvc, mocker, caplog): +def test_plots_diff_vega(dvc, mocker, capsys): cli_args = parse_args( [ "plots", @@ -108,10 +107,12 @@ def test_plots_diff_vega(dvc, mocker, caplog): "dvc.repo.plots.diff.diff", return_value={"plots.csv": "plothtml"} ) assert cmd.run() == 0 - assert "plothtml" in caplog.text + + out, _ = capsys.readouterr() + assert "plothtml" in out -def test_plots_diff_open(tmp_dir, dvc, mocker, caplog): +def test_plots_diff_open(tmp_dir, dvc, mocker, capsys): mocked_open = mocker.patch("webbrowser.open", return_value=True) cli_args = parse_args(["plots", "diff", "--targets", "datafile", "--open"]) cmd = cli_args.func(cli_args) @@ -123,10 +124,12 @@ def test_plots_diff_open(tmp_dir, dvc, mocker, caplog): mocked_open.assert_called_once_with("plots.html") expected_url = posixpath.join(tmp_dir.as_uri(), "plots.html") - assert expected_url in caplog.text + + out, _ = capsys.readouterr() + assert expected_url in out -def test_plots_diff_open_failed(tmp_dir, dvc, mocker, caplog): +def test_plots_diff_open_failed(tmp_dir, dvc, mocker, capsys): mocked_open = mocker.patch("webbrowser.open", return_value=False) cli_args = parse_args(["plots", "diff", "--targets", "datafile", "--open"]) cmd = cli_args.func(cli_args) @@ -140,10 +143,9 @@ def test_plots_diff_open_failed(tmp_dir, dvc, mocker, caplog): error_message = "Failed to open. Please try opening it manually." expected_url = posixpath.join(tmp_dir.as_uri(), "plots.html") - assert caplog.record_tuples == [ - ("dvc.command.plots", logging.INFO, expected_url), - ("dvc.command.plots", logging.ERROR, error_message), - ] + out, err = capsys.readouterr() + assert expected_url in out + assert error_message in err @pytest.mark.parametrize( @@ -155,7 +157,7 @@ def test_plots_diff_open_failed(tmp_dir, dvc, mocker, caplog): ids=["quote", "resolve"], ) def test_plots_path_is_quoted_and_resolved_properly( - tmp_dir, dvc, mocker, caplog, output, expected_url_path + tmp_dir, dvc, mocker, capsys, output, expected_url_path ): cli_args = parse_args( ["plots", "diff", "--targets", "datafile", "--out", output] @@ -167,4 +169,6 @@ def test_plots_path_is_quoted_and_resolved_properly( assert cmd.run() == 0 expected_url = posixpath.join(tmp_dir.as_uri(), expected_url_path) - assert expected_url in caplog.text + + out, _ = capsys.readouterr() + assert expected_url in out diff --git a/tests/unit/command/test_status.py b/tests/unit/command/test_status.py index d6a370270..bf92bac1d 100644 --- a/tests/unit/command/test_status.py +++ b/tests/unit/command/test_status.py @@ -45,16 +45,17 @@ def test_cloud_status(tmp_dir, dvc, mocker): @pytest.mark.parametrize("status", [{}, {"a": "b", "c": [1, 2, 3]}, [1, 2, 3]]) -def test_status_show_json(dvc, mocker, caplog, status): +def test_status_show_json(dvc, mocker, capsys, status): cli_args = parse_args(["status", "--show-json"]) assert cli_args.func == CmdDataStatus cmd = cli_args.func(cli_args) mocker.patch.object(cmd.repo, "status", autospec=True, return_value=status) - caplog.clear() + assert cmd.run() == 0 - assert caplog.messages == [json.dumps(status)] + out, _ = capsys.readouterr() + assert json.dumps(status) in out @pytest.mark.parametrize(