Repository: fsspec/universal_pathlib (python)

Issue:
Inconsistency with Path
These two examples should probably behave identically, but they don't :

`Path("a", "b") ` , `Path("a") / "b"`, `UPath("a", "b"), and `UPath("a")  / "b"`  produce same result: "a/b" (as expected).

However

`UPath("s3://a", "b")` results in "s3://ab" (no separator), which is, probably, a bug. 

Pull request description:
Fix parsed url issues
Closes #93 #95

Includes changes made in #94 

Patch:
diff --git a/upath/core.py b/upath/core.py
index a7c546e..b4023b3 100644
--- a/upath/core.py
+++ b/upath/core.py
@@ -176,6 +176,9 @@ class UPath(Path):
 
         url = stringify_path(other)
         parsed_url = urlsplit(url)
+        if not parsed_url.path:
+            parsed_url = parsed_url._replace(path="/")  # ensure path has root
+
         for key in ["scheme", "netloc"]:
             val = kwargs.get(key)
             if val:
@@ -551,7 +554,6 @@ class UPath(Path):
         obj._drv = drv
         if sys.version_info < (3, 9):
             obj._closed = False
-        obj._url = url
         obj._kwargs = kwargs.copy()
 
         if not root:
@@ -563,6 +565,11 @@ class UPath(Path):
         obj._root = root
         obj._parts = parts
 
+        # Update to (full) URL
+        if url:
+            url = url._replace(path=root + cls._flavour.join(parts[1:]))
+        obj._url = url
+
         return obj
 
     @classmethod
@@ -579,7 +586,6 @@ class UPath(Path):
         obj._parts = parts
         if sys.version_info < (3, 9):
             obj._closed = False
-        obj._url = url
         obj._kwargs = kwargs.copy()
 
         if not root:
@@ -589,8 +595,11 @@ class UPath(Path):
                 root = parts.pop(0)
         if len(obj._parts) == 0 or obj._parts[0] != root:
             obj._parts.insert(0, root)
-
         obj._root = root
+
+        if url:
+            url = url._replace(path=root + cls._flavour.join(parts[1:]))
+        obj._url = url
         return obj
 
     def __str__(self) -> str:


Test patch:
diff --git a/upath/tests/cases.py b/upath/tests/cases.py
index 4b00ba7..9f00d8f 100644
--- a/upath/tests/cases.py
+++ b/upath/tests/cases.py
@@ -406,3 +406,10 @@ class BaseTests:
         p = self.path.joinpath("folder1")
         with pytest.raises(OSError, match="not empty"):
             p.rmdir(recursive=False)
+
+    def test_private_url_attr_in_sync(self):
+        p = self.path
+        p1 = self.path.joinpath("c")
+        p2 = self.path / "c"
+        assert p1._url == p2._url
+        assert p1 != p._url
diff --git a/upath/tests/test_core.py b/upath/tests/test_core.py
index a7ddf4d..6a2adf5 100644
--- a/upath/tests/test_core.py
+++ b/upath/tests/test_core.py
@@ -5,7 +5,7 @@ import warnings
 
 import pytest
 from upath import UPath
-from upath.implementations.cloud import S3Path
+from upath.implementations.cloud import S3Path, GCSPath
 from .cases import BaseTests
 from .utils import only_on_windows, skip_on_windows
 
@@ -121,7 +121,7 @@ PATHS = (
     (
         ("/tmp/abc", (), None, pathlib.Path),
         ("s3://bucket/folder", ({"anon": True}), "s3fs", S3Path),
-        ("gs://bucket/folder", ({"token": "anon"}), "gcsfs", UPath),
+        ("gs://bucket/folder", ({"token": "anon"}), "gcsfs", GCSPath),
     ),
 )
 
@@ -150,6 +150,17 @@ def test_create_from_type(path, storage_options, module, object_type):
         pass
 
 
+def test_list_args():
+    path_a = UPath("gcs://bucket", "folder")
+    path_b = UPath("gcs://bucket") / "folder"
+
+    assert str(path_a) == str(path_b)
+    assert path_a._root == path_b._root
+    assert path_a._drv == path_b._drv
+    assert path_a._parts == path_b._parts
+    assert path_a._url == path_b._url
+
+
 def test_child_path():
     path_a = UPath("gcs://bucket/folder")
     path_b = UPath("gcs://bucket") / "folder"