From 6c27685986111733ed8045708781fc0caa0cfec7 Mon Sep 17 00:00:00 2001 From: itzzdev09 Date: Thu, 17 Sep 2026 17:11:55 +0530 Subject: [PATCH] Keep the server in the path of UNC file URIs on Windows pathlib writes \server\share\file as file://server/share/file, which fsspec reads as a relative local path. Return file:////server/share/file from WindowsUPath.as_uri so the URI round-trips through UPath. --- upath/implementations/local.py | 10 ++++++++++ upath/tests/pathlib/test_pathlib_310.py | 19 +++++++++++++++++++ upath/tests/pathlib/test_pathlib_311.py | 19 +++++++++++++++++++ upath/tests/pathlib/test_pathlib_312.py | 19 +++++++++++++++++++ upath/tests/pathlib/test_pathlib_39.py | 19 +++++++++++++++++++ upath/tests/test_core.py | 9 +++++++++ 6 files changed, 95 insertions(+) diff --git a/upath/implementations/local.py b/upath/implementations/local.py index 884199d9..211757cb 100644 --- a/upath/implementations/local.py +++ b/upath/implementations/local.py @@ -758,6 +758,16 @@ class PosixUPath(LocalPath, pathlib.PosixPath, UPath): # type: ignore[misc] class WindowsUPath(LocalPath, pathlib.WindowsPath): __slots__ = () + def as_uri(self) -> str: + uri = super().as_uri() + # pathlib writes UNC paths as file://server/share/..., but fsspec + # reads the part after file:// as a path, so that URI would point + # at a local server/share/... directory. Keep the server in the + # path instead: file:////server/share/... + if uri.startswith("file://") and not uri.startswith("file:///"): + uri = "file:////" + uri[len("file://") :] + return uri + if os.name != "nt": def __new__( diff --git a/upath/tests/pathlib/test_pathlib_310.py b/upath/tests/pathlib/test_pathlib_310.py index e603e495..da57d36f 100644 --- a/upath/tests/pathlib/test_pathlib_310.py +++ b/upath/tests/pathlib/test_pathlib_310.py @@ -1231,6 +1231,25 @@ class PosixPathAsPureTest(PurePosixPathTest, unittest.TestCase): class WindowsPathAsPureTest(PureWindowsPathTest, unittest.TestCase): cls = WindowsUPath + def test_as_uri(self): + # UPath keeps the server of a UNC path in the URI path + # (file:////server/share) where pathlib uses file://server/share, + # because fsspec reads file://server/share as a relative path + P = self.cls + with self.assertRaises(ValueError): + P('/a/b').as_uri() + with self.assertRaises(ValueError): + P('c:a/b').as_uri() + self.assertEqual(P('c:/').as_uri(), 'file:///c:/') + self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') + self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') + self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') + self.assertEqual(P('//some/share/').as_uri(), 'file:////some/share/') + self.assertEqual(P('//some/share/a/b.c').as_uri(), + 'file:////some/share/a/b.c') + self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), + 'file:////some/share/a/b%25%23c%C3%A9') + def test_owner(self): P = self.cls with self.assertRaises(NotImplementedError): diff --git a/upath/tests/pathlib/test_pathlib_311.py b/upath/tests/pathlib/test_pathlib_311.py index 4de8abef..bbd283d3 100644 --- a/upath/tests/pathlib/test_pathlib_311.py +++ b/upath/tests/pathlib/test_pathlib_311.py @@ -1233,6 +1233,25 @@ class PosixPathAsPureTest(PurePosixPathTest, unittest.TestCase): class WindowsPathAsPureTest(PureWindowsPathTest, unittest.TestCase): cls = WindowsUPath + def test_as_uri(self): + # UPath keeps the server of a UNC path in the URI path + # (file:////server/share) where pathlib uses file://server/share, + # because fsspec reads file://server/share as a relative path + P = self.cls + with self.assertRaises(ValueError): + P('/a/b').as_uri() + with self.assertRaises(ValueError): + P('c:a/b').as_uri() + self.assertEqual(P('c:/').as_uri(), 'file:///c:/') + self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') + self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') + self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') + self.assertEqual(P('//some/share/').as_uri(), 'file:////some/share/') + self.assertEqual(P('//some/share/a/b.c').as_uri(), + 'file:////some/share/a/b.c') + self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), + 'file:////some/share/a/b%25%23c%C3%A9') + def test_owner(self): P = self.cls with self.assertRaises(NotImplementedError): diff --git a/upath/tests/pathlib/test_pathlib_312.py b/upath/tests/pathlib/test_pathlib_312.py index c362e72d..9fe030e1 100644 --- a/upath/tests/pathlib/test_pathlib_312.py +++ b/upath/tests/pathlib/test_pathlib_312.py @@ -1541,6 +1541,25 @@ class PosixPathAsPureTest(PurePosixPathTest, unittest.TestCase): class WindowsPathAsPureTest(PureWindowsPathTest, unittest.TestCase): cls = WindowsUPath + def test_as_uri(self): + # UPath keeps the server of a UNC path in the URI path + # (file:////server/share) where pathlib uses file://server/share, + # because fsspec reads file://server/share as a relative path + P = self.cls + with self.assertRaises(ValueError): + P('/a/b').as_uri() + with self.assertRaises(ValueError): + P('c:a/b').as_uri() + self.assertEqual(P('c:/').as_uri(), 'file:///c:/') + self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') + self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') + self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') + self.assertEqual(P('//some/share/').as_uri(), 'file:////some/share/') + self.assertEqual(P('//some/share/a/b.c').as_uri(), + 'file:////some/share/a/b.c') + self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), + 'file:////some/share/a/b%25%23c%C3%A9') + def test_owner(self): P = self.cls with self.assertRaises(NotImplementedError): diff --git a/upath/tests/pathlib/test_pathlib_39.py b/upath/tests/pathlib/test_pathlib_39.py index 69d0df81..61567ac8 100644 --- a/upath/tests/pathlib/test_pathlib_39.py +++ b/upath/tests/pathlib/test_pathlib_39.py @@ -1192,6 +1192,25 @@ class PosixPathAsPureTest(PurePosixPathTest, unittest.TestCase): class WindowsPathAsPureTest(PureWindowsPathTest, unittest.TestCase): cls = WindowsUPath + def test_as_uri(self): + # UPath keeps the server of a UNC path in the URI path + # (file:////server/share) where pathlib uses file://server/share, + # because fsspec reads file://server/share as a relative path + P = self.cls + with self.assertRaises(ValueError): + P('/a/b').as_uri() + with self.assertRaises(ValueError): + P('c:a/b').as_uri() + self.assertEqual(P('c:/').as_uri(), 'file:///c:/') + self.assertEqual(P('c:/a/b.c').as_uri(), 'file:///c:/a/b.c') + self.assertEqual(P('c:/a/b%#c').as_uri(), 'file:///c:/a/b%25%23c') + self.assertEqual(P('c:/a/b\xe9').as_uri(), 'file:///c:/a/b%C3%A9') + self.assertEqual(P('//some/share/').as_uri(), 'file:////some/share/') + self.assertEqual(P('//some/share/a/b.c').as_uri(), + 'file:////some/share/a/b.c') + self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), + 'file:////some/share/a/b%25%23c%C3%A9') + def test_owner(self): P = self.cls with self.assertRaises(NotImplementedError): diff --git a/upath/tests/test_core.py b/upath/tests/test_core.py index c1805df8..006f7bd7 100644 --- a/upath/tests/test_core.py +++ b/upath/tests/test_core.py @@ -33,6 +33,15 @@ def test_windows_path(local_testdir): assert isinstance(UPath(local_testdir), pathlib.WindowsPath) +@only_on_windows +def test_windows_unc_path_as_uri_round_trip(): + # https://github.com/fsspec/universal_pathlib/issues/557 + pth = UPath(r"\\server\share\file") + uri = pth.as_uri() + assert uri == "file:////server/share/file" + assert UPath(uri).path == "//server/share/file" + + def test_UPath_untested_protocol_warning(clear_registry): with warnings.catch_warnings(record=True) as w: _ = UPath("mock:/")