Repository: iterative/dvc

Issue:
pull: can't do partial checkouts
Discourse context: https://discuss.dvc.org/t/how-to-pull-data-from-gcs-without-pipelines/697/3
Reprocuder;
```
 $ dvc pull baz/bar -v                               
2021-03-09 15:17:11,217 DEBUG: Check for update is enabled.
2021-03-09 15:17:11,234 DEBUG: Checking if stage 'baz/bar' is in 'dvc.yaml'
2021-03-09 15:17:12,011 DEBUG: Preparing to download data from 'gs://batuhan-test/'
2021-03-09 15:17:12,012 DEBUG: Preparing to collect status from gs://batuhan-test/
2021-03-09 15:17:12,012 DEBUG: Collecting information from local cache...
2021-03-09 15:17:12,015 DEBUG: state save (4070797, 1615292181377594880, 200) 53e90b7e63e793997644b8d25ab13618.dir                  
2021-03-09 15:17:12,020 DEBUG: state save (4070801, 1615292182017608704, 4) 258622b1688250cb619f3c9ccaefb7eb
2021-03-09 15:17:12,021 DEBUG: Checking if stage 'baz/bar' is in 'dvc.yaml'
('bar', 'baz')
2021-03-09 15:17:12,032 ERROR: unexpected error - unsupported operand type(s) for /: 'PosixPathInfo' and 'tuple'
------------------------------------------------------------
Traceback (most recent call last):
  File "/home/isidentical/dvc/dvc/main.py", line 50, in main
    ret = cmd.run()
  File "/home/isidentical/dvc/dvc/command/data_sync.py", line 29, in run
    stats = self.repo.pull(
  File "/home/isidentical/dvc/dvc/repo/__init__.py", line 49, in wrapper
    return f(repo, *args, **kwargs)
  File "/home/isidentical/dvc/dvc/repo/pull.py", line 40, in pull
    stats = self.checkout(
  File "/home/isidentical/dvc/dvc/repo/__init__.py", line 49, in wrapper
    return f(repo, *args, **kwargs)
  File "/home/isidentical/dvc/dvc/repo/checkout.py", line 104, in checkout
    total = get_all_files_numbers(pairs)
  File "/home/isidentical/dvc/dvc/repo/checkout.py", line 45, in get_all_files_numbers
    return sum(
  File "/home/isidentical/dvc/dvc/repo/checkout.py", line 46, in <genexpr>
    stage.get_all_files_number(filter_info) for stage, filter_info in pairs
  File "/home/isidentical/dvc/dvc/stage/__init__.py", line 611, in get_all_files_number
    return sum(
  File "/home/isidentical/dvc/dvc/stage/__init__.py", line 612, in <genexpr>
    out.get_files_number(filter_info)
  File "/home/isidentical/dvc/dvc/output/base.py", line 477, in get_files_number
    return ilen(
  File "/home/isidentical/.venv38/lib/python3.8/site-packages/funcy/seqs.py", line 105, in ilen
    deque(zip(seq, counter), maxlen=0)  # (consume at C speed)
  File "/home/isidentical/dvc/dvc/output/base.py", line 478, in <genexpr>
    print(relpath) or filter_info.isin_or_eq(self.path_info / relpath)
TypeError: unsupported operand type(s) for /: 'PosixPathInfo' and 'tuple'
------------------------------------------------------------
2021-03-09 15:17:12,353 DEBUG: Version info for developers:
DVC version: 2.0.3+e0a034.mod 
---------------------------------
Platform: Python 3.8.5+ on Linux-5.4.0-66-generic-x86_64-with-glibc2.29
Supports: All remotes
Cache types: hardlink, symlink
Cache directory: ext4 on /dev/sda7
Caches: local
Remotes: gs
Workspace directory: ext4 on /dev/sda7
Repo: dvc, git

Having any troubles? Hit us up at https://dvc.org/support, we are always happy to help!
```


Discussion:
Again my fault, broke `get_files_number` during obj refactor :slightly_frowning_face: . This method will go away in the future, but we should def fix it ad-hoc for now.

> This method will go away in the future, but we should def fix it ad-hoc for now.

Alright, will send one. 

Resolving patch:
diff --git a/dvc/output/base.py b/dvc/output/base.py
--- a/dvc/output/base.py
+++ b/dvc/output/base.py
@@ -475,7 +475,7 @@ def get_files_number(self, filter_info=None):
             return 0
 
         return ilen(
-            filter_info.isin_or_eq(self.path_info / relpath)
+            filter_info.isin_or_eq(self.path_info.joinpath(*relpath))
             for relpath, _ in self.hash_info.dir_info.items()
         )
 


Test patch:
diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py
--- a/tests/func/test_data_cloud.py
+++ b/tests/func/test_data_cloud.py
@@ -537,3 +537,13 @@ def test_push_pull_fetch_pipeline_stages(tmp_dir, dvc, run_copy, local_remote):
 
     dvc.fetch("copy-foo-bar")
     assert len(recurse_list_dir(dvc.odb.local.cache_dir)) == 1
+
+
+def test_pull_partial(tmp_dir, dvc, local_remote):
+    tmp_dir.dvc_gen({"foo": {"bar": {"baz": "baz"}, "spam": "spam"}})
+    dvc.push()
+    clean(["foo"], dvc)
+
+    stats = dvc.pull(os.path.join("foo", "bar"))
+    assert stats["fetched"] == 1
+    assert (tmp_dir / "foo").read_text() == {"bar": {"baz": "baz"}}