Repository: iterative/dvc

Issue:
exp show: `--exclude` throws AttributeError
## Bug Report

### Please provide information about your setup
```
dvc exp show --exclude-metrics train -v
2020-11-22 14:31:39,852 DEBUG: Check for update is enabled.
2020-11-22 14:31:39,880 DEBUG: fetched: [(3,)]                        
2020-11-22 14:31:41,553 DEBUG: fetched: [(12331,)]
2020-11-22 14:31:42,627 ERROR: unexpected error - 'collections.OrderedDict' object has no attribute 'difference_update'
------------------------------------------------------------
Traceback (most recent call last):
  File "/home/saugat/repos/iterative/dvc/dvc/main.py", line 90, in main
    ret = cmd.run()
  File "/home/saugat/repos/iterative/dvc/dvc/command/experiments.py", line 335, in run
    _show_experiments(
  File "/home/saugat/repos/iterative/dvc/dvc/command/experiments.py", line 265, in _show_experiments
    metric_names, param_names = _collect_names(
  File "/home/saugat/repos/iterative/dvc/dvc/command/experiments.py", line 84, in _collect_names
    metric_names = _filter_names(
  File "/home/saugat/repos/iterative/dvc/dvc/command/experiments.py", line 61, in _filter_names
    _filter(exclude, ret.difference_update)
AttributeError: 'collections.OrderedDict' object has no attribute 'difference_update'
------------------------------------------------------------
```



Discussion:

Resolving patch:
diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py
--- a/dvc/command/experiments.py
+++ b/dvc/command/experiments.py
@@ -56,7 +56,11 @@ def _filter(filters, update_func):
         ret = OrderedDict({name: None for name in names})
 
     if exclude:
-        _filter(exclude, ret.difference_update)  # type: ignore[attr-defined]
+        to_remove = {}
+        _filter(exclude, to_remove.update)
+        for key in to_remove:
+            if key in ret:
+                del ret[key]
 
     return [".".join(name) for name in ret]
 


Test patch:
diff --git a/tests/func/experiments/test_show.py b/tests/func/experiments/test_show.py
--- a/tests/func/experiments/test_show.py
+++ b/tests/func/experiments/test_show.py
@@ -1,23 +1,13 @@
+import os
 from datetime import datetime
 
 from funcy import first
 
-from dvc.dvcfile import PIPELINE_FILE
 from dvc.main import main
 from dvc.repo.experiments.base import ExpRefInfo
-from tests.func.test_repro_multistage import COPY_SCRIPT
 
 
-def test_show_simple(tmp_dir, scm, dvc):
-    tmp_dir.gen("copy.py", COPY_SCRIPT)
-    tmp_dir.gen("params.yaml", "foo: 1")
-    dvc.run(
-        cmd="python copy.py params.yaml metrics.yaml",
-        metrics_no_cache=["metrics.yaml"],
-        params=["foo"],
-        single_stage=True,
-    )
-
+def test_show_simple(tmp_dir, scm, dvc, exp_stage):
     assert dvc.experiments.show()["workspace"] == {
         "baseline": {
             "metrics": {"metrics.yaml": {"foo": 1}},
@@ -28,23 +18,13 @@ def test_show_simple(tmp_dir, scm, dvc):
     }
 
 
-def test_show_experiment(tmp_dir, scm, dvc):
-    tmp_dir.gen("copy.py", COPY_SCRIPT)
-    tmp_dir.gen("params.yaml", "foo: 1")
-    dvc.run(
-        cmd="python copy.py params.yaml metrics.yaml",
-        metrics_no_cache=["metrics.yaml"],
-        params=["foo"],
-        name="foo",
-    )
-    scm.add(["copy.py", "params.yaml", "metrics.yaml", "dvc.yaml", "dvc.lock"])
-    scm.commit("baseline")
+def test_show_experiment(tmp_dir, scm, dvc, exp_stage):
     baseline_rev = scm.get_rev()
     timestamp = datetime.fromtimestamp(
         scm.gitpython.repo.rev_parse(baseline_rev).committed_date
     )
 
-    dvc.experiments.run(PIPELINE_FILE, params=["foo=2"])
+    dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
     results = dvc.experiments.show()
 
     expected_baseline = {
@@ -67,22 +47,12 @@ def test_show_experiment(tmp_dir, scm, dvc):
             assert exp["params"]["params.yaml"] == expected_params
 
 
-def test_show_queued(tmp_dir, scm, dvc):
+def test_show_queued(tmp_dir, scm, dvc, exp_stage):
     from dvc.repo.experiments.base import EXPS_STASH
 
-    tmp_dir.gen("copy.py", COPY_SCRIPT)
-    tmp_dir.gen("params.yaml", "foo: 1")
-    stage = dvc.run(
-        cmd="python copy.py params.yaml metrics.yaml",
-        metrics_no_cache=["metrics.yaml"],
-        params=["foo"],
-        name="foo",
-    )
-    scm.add(["copy.py", "params.yaml", "metrics.yaml", "dvc.yaml", "dvc.lock"])
-    scm.commit("baseline")
     baseline_rev = scm.get_rev()
 
-    dvc.experiments.run(stage.addressing, params=["foo=2"], queue=True)
+    dvc.experiments.run(exp_stage.addressing, params=["foo=2"], queue=True)
     exp_rev = dvc.experiments.scm.resolve_rev(f"{EXPS_STASH}@{{0}}")
 
     results = dvc.experiments.show()[baseline_rev]
@@ -97,7 +67,7 @@ def test_show_queued(tmp_dir, scm, dvc):
     scm.commit("new commit")
     new_rev = scm.get_rev()
 
-    dvc.experiments.run(stage.addressing, params=["foo=3"], queue=True)
+    dvc.experiments.run(exp_stage.addressing, params=["foo=3"], queue=True)
     exp_rev = dvc.experiments.scm.resolve_rev(f"{EXPS_STASH}@{{0}}")
 
     results = dvc.experiments.show()[new_rev]
@@ -168,3 +138,41 @@ def test_show_checkpoint_branch(tmp_dir, scm, dvc, checkpoint_stage, capsys):
         name = ref_info.name
         assert f"╓ {name}" in cap.out
     assert f"({branch_rev[:7]})" in cap.out
+
+
+def test_show_filter(tmp_dir, scm, dvc, exp_stage, capsys):
+    capsys.readouterr()
+    assert (
+        main(
+            [
+                "exp",
+                "show",
+                "--no-pager",
+                "--no-timestamp",
+                "--include-metrics=foo",
+                "--include-params=foo",
+            ]
+        )
+        == 0
+    )
+    cap = capsys.readouterr()
+
+    div = "│" if os.name == "nt" else "┃"
+    assert f"{div} foo {div} foo {div}" in cap.out
+
+    assert (
+        main(
+            [
+                "exp",
+                "show",
+                "--no-pager",
+                "--no-timestamp",
+                "--exclude-metrics=foo",
+                "--exclude-params=foo",
+            ]
+        )
+        == 0
+    )
+    cap = capsys.readouterr()
+
+    assert f"{div} foo {div}" not in cap.out