Repository: iterative/dvc

Issue:
dvc.fs.Path.parts wrong results
**EDIT**: This issue will just be for this first problem of handling a sep at the end of a path. I made the windows-style path problem a separate issue #7233 

When a path ends with the path sep, the `parts` function doesn't split. It returns a tuple with a single item:
```python
from dvc.fs.path import Path
Path('/').parts('/a/b/c/')
```
```python
('/a/b/c',)
```

A second problem occurs when using windows style paths. We get the sep between the drive and the rest of the path:
```python
Path('\\').parts('c:\\a')
```
```python
('c:', '\\', 'a')
```

The first problem could be solved by simply stripping the final sep:
```python
        drive, path = self.flavour.splitdrive(path.rstrip(self.flavour.sep))
```
but the second problem would still exist.


We should really get these results:
```python
Path('/').parts('/a/b/c/')
```
```python
('/', 'a', 'b', 'c')
```
and
```python
Path('\\').parts('c:\\a')
```
```python
('c:', 'a')
```

Note the second case is still a little different from pathlib, which would include the sep with the drive:
```python
from pathlib import PureWindowsPath
PureWindowsPath('c:\\a').parts
```
```python
('c:\\', 'a')
```
but this is probably more in-line with fsspec, which basically treats the drive letter as the first element of a relative path:
```python
fsspec.AbstractFileSystem._parent('c:/a')
``` 
```python
'c:'
``` 


version info:
```
DVC version: 2.9.4.dev28+gd90fe54d.d20220106 
---------------------------------
Platform: Python 3.10.1 on Linux-5.15.11-arch2-1-x86_64-with-glibc2.33
Supports:
	azure (adlfs = 2021.10.0, knack = 0.9.0, azure-identity = 1.7.1),
	gdrive (pydrive2 = 1.10.0),
	gs (gcsfs = 2021.11.1),
	hdfs (fsspec = 2021.11.1, pyarrow = 6.0.1),
	webhdfs (fsspec = 2021.11.1),
	http (aiohttp = 3.8.1, aiohttp-retry = 2.4.6),
	https (aiohttp = 3.8.1, aiohttp-retry = 2.4.6),
	s3 (s3fs = 2021.11.1, boto3 = 1.19.8),
	ssh (sshfs = 2021.11.2),
	oss (ossfs = 2021.8.0),
	webdav (webdav4 = 0.9.3),
	webdavs (webdav4 = 0.9.3)
Cache types: <https://error.dvc.org/no-dvc-cache>
Caches: local
Remotes: https
Workspace directory: btrfs on /dev/mapper/nvme0n1p3_crypt
Repo: dvc, git
```


Discussion:

Resolving patch:
diff --git a/dvc/fs/path.py b/dvc/fs/path.py
--- a/dvc/fs/path.py
+++ b/dvc/fs/path.py
@@ -15,7 +15,7 @@ def join(self, *parts):
         return self.flavour.join(*parts)
 
     def parts(self, path):
-        drive, path = self.flavour.splitdrive(path)
+        drive, path = self.flavour.splitdrive(path.rstrip(self.flavour.sep))
 
         ret = []
         while True:


Test patch:
diff --git a/tests/unit/fs/test_path.py b/tests/unit/fs/test_path.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/fs/test_path.py
@@ -0,0 +1,29 @@
+import pytest
+
+from dvc.fs.path import Path
+
+
+@pytest.mark.parametrize("prefix", ["", "/"])
+@pytest.mark.parametrize("postfix", ["", "/"])
+@pytest.mark.parametrize(
+    "path,expected",
+    [
+        ("path", ("path",)),
+        ("some/path", ("some", "path")),
+    ],
+)
+def test_parts_posix(prefix, postfix, path, expected):
+    assert Path("/").parts(prefix + path + postfix) == tuple(prefix) + expected
+
+
+@pytest.mark.parametrize("postfix", ["", "\\"])
+@pytest.mark.parametrize(
+    "path,expected",
+    [
+        ("path", ("path",)),
+        ("c:\\path", ("c:", "\\", "path")),
+        ("some\\path", ("some", "path")),
+    ],
+)
+def test_parts_nt(postfix, path, expected):
+    assert Path("\\").parts(path + postfix) == expected