Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions upath/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ def __new__(cls, *args, **kwargs) -> Union["UPath", pathlib.Path]:
other._drv, other._root, other._parts, drv, root, parts
)

new_kwargs = getattr(other, "_kwargs", {}).copy()
_kwargs = getattr(other, "_kwargs", {})
_url = getattr(other, "_url", None)
other_kwargs = _kwargs.copy()
if _url:
other_kwargs["url"] = _url
new_kwargs = _kwargs.copy()
new_kwargs.update(kwargs)

return other.__class__(
other._format_parsed_parts(drv, root, parts),
other._format_parsed_parts(drv, root, parts, **other_kwargs),
**new_kwargs,
)

Expand Down Expand Up @@ -150,16 +155,21 @@ def _make_child_relpath(self, part):
self._drv, self._root, parts, url=self._url, **self._kwargs
)

def _format_parsed_parts(self, drv, root, parts):
@classmethod
def _format_parsed_parts(cls, drv, root, parts, url=None, **kwargs):
if parts:
join_parts = parts[1:] if parts[0] == "/" else parts
else:
join_parts = []
if drv or root:
path = drv + root + self._flavour.join(join_parts)
path = drv + root + cls._flavour.join(join_parts)
else:
path = self._flavour.join(join_parts)
scheme, netloc = self._url.scheme, self._url.netloc
path = cls._flavour.join(join_parts)
if not url:
scheme = kwargs.get("scheme", "file")
netloc = kwargs.get("netloc")
else:
scheme, netloc = url.scheme, url.netloc
scheme = scheme + ":"
netloc = "//" + netloc if netloc else ""
formatted = scheme + netloc + path
Expand Down Expand Up @@ -449,6 +459,21 @@ def _from_parsed_parts(cls, drv, root, parts, url=None, **kwargs):
obj._root = root
return obj

def __str__(self):
"""Return the string representation of the path, suitable for
passing to system calls."""
try:
return self._str
except AttributeError:
self._str = self._format_parsed_parts(
self._drv,
self._root,
self._parts,
url=self._url,
**self._kwargs,
)
return self._str

@property
def fs(self):
return self._accessor._fs
Expand All @@ -468,7 +493,7 @@ def __truediv__(self, key):

# Create a new object
out = self.__class__(
self._format_parsed_parts(drv, root, parts),
self._format_parsed_parts(drv, root, parts, url=self._url),
**kwargs,
)
return out
Expand All @@ -477,9 +502,14 @@ def __setstate__(self, state):
self._kwargs = state["_kwargs"].copy()

def __reduce__(self):
cls = type(self)
return (
self.__class__,
(self._format_parsed_parts(self._drv, self._root, self._parts),),
cls,
(
cls._format_parsed_parts(
self._drv, self._root, self._parts, url=self._url
),
),
{"_kwargs": self._kwargs.copy()},
)

Expand Down
2 changes: 1 addition & 1 deletion upath/implementations/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def _from_parsed_parts(cls, drv, root, parts, url=None, **kwargs):
if kwargs.get("bucket") and url is not None:
bucket = kwargs.pop("bucket")
url = url._replace(netloc=bucket)
obj = super()._from_parsed_parts(drv, root, parts, url, **kwargs)
obj = super()._from_parsed_parts(drv, root, parts, url=url, **kwargs)
return obj

def _sub_path(self, name):
Expand Down
4 changes: 3 additions & 1 deletion upath/implementations/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ def _sub_path(self, name):
`listdir` and `glob`. However, in `iterdir` and `glob` we only want the
relative path to `self`.
"""
complete_address = self._format_parsed_parts(None, None, [self.path])
complete_address = self._format_parsed_parts(
None, None, [self.path], url=self._url, **self._kwargs
)

if name.startswith(complete_address):
name = name[len(complete_address) :] # noqa: E203
Expand Down
4 changes: 3 additions & 1 deletion upath/implementations/webdav.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ def _sub_path(self, name):
and glob, so we potentially need to sub the whole string
"""
sp = self.path
complete_address = self._format_parsed_parts(None, None, [sp])
complete_address = self._format_parsed_parts(
None, None, [sp], url=self._url, **self._kwargs
)

if name.startswith(complete_address):
name = name[len(complete_address) :] # noqa: E203
Expand Down
11 changes: 7 additions & 4 deletions upath/tests/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

import pytest
from upath import UPath
from .utils import posixify


class BaseTests:
SUPPORTS_EMPTY_DIRS = True

path: UPath

def test_cwd(self):
with pytest.raises(NotImplementedError):
self.path.cwd()
Expand Down Expand Up @@ -42,11 +43,13 @@ def test_glob(self, pathlib_base):
mock_glob = list(self.path.glob("**.txt"))
path_glob = list(pathlib_base.glob("**/*.txt"))

_mock_start = len(self.path.parts)
mock_glob_normalized = sorted(
[a.relative_to(self.path).path for a in mock_glob]
[a.parts[_mock_start:] for a in mock_glob]
)
_path_start = len(pathlib_base.parts)
path_glob_normalized = sorted(
[f"/{posixify(a.relative_to(pathlib_base))}" for a in path_glob]
[a.parts[_path_start:] for a in path_glob]
)

print(mock_glob_normalized, path_glob_normalized)
Expand Down Expand Up @@ -285,7 +288,7 @@ def test_pickling(self):
assert path.fs.storage_options == recovered_path.fs.storage_options

def test_pickling_child_path(self):
path = (self.path) / "subfolder" / "subsubfolder"
path = self.path / "subfolder" / "subsubfolder"
pickled_path = pickle.dumps(path)
recovered_path = pickle.loads(pickled_path)

Expand Down
72 changes: 39 additions & 33 deletions upath/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
from fsspec.implementations.local import LocalFileSystem
from fsspec.registry import register_implementation, _registry

from azure.storage.blob import BlobServiceClient

import fsspec

from .utils import posixify
Expand Down Expand Up @@ -301,42 +299,49 @@ def http_fixture(local_testdir, http_server):


@pytest.fixture(scope="session")
def webdav_server():
from wsgidav.wsgidav_app import WsgiDAVApp
from cheroot import wsgi

with tempfile.TemporaryDirectory() as tempdir:
host = "127.0.0.1"
port = 8090
app = WsgiDAVApp(
{
"host": host,
"port": port,
"provider_mapping": {"/": tempdir},
"simple_dc": {
"user_mapping": {"*": {"USER": {"password": "PASSWORD"}}}
},
}
)
srvr = wsgi.Server(bind_addr=(host, port), wsgi_app=app)
srvr.prepare()
thread = threading.Thread(target=srvr.serve)
thread.daemon = True
thread.start()
def webdav_server(tmp_path_factory):
try:
from wsgidav.wsgidav_app import WsgiDAVApp
from cheroot import wsgi
except ImportError as err:
pytest.skip(str(err))

tempdir = str(tmp_path_factory.mktemp("webdav"))

host = "127.0.0.1"
port = 8090
app = WsgiDAVApp(
{
"host": host,
"port": port,
"provider_mapping": {"/": tempdir},
"simple_dc": {
"user_mapping": {"*": {"USER": {"password": "PASSWORD"}}}
},
}
)
srvr = wsgi.Server(bind_addr=(host, port), wsgi_app=app)
srvr.prepare()
thread = threading.Thread(target=srvr.serve, daemon=True)
thread.start()

try:
yield tempdir, f"webdav+http://{host}:{port}"
finally:
srvr.stop()
thread.join()
try:
yield tempdir, f"webdav+http://{host}:{port}"
finally:
srvr.stop()
thread.join()


@pytest.fixture
def webdav_fixture(local_testdir, webdav_server):
webdav_path, webdav_url = webdav_server
shutil.rmtree(webdav_path)
shutil.copytree(local_testdir, webdav_path)
yield webdav_url
if os.path.isdir(webdav_path):
os.rmdir(webdav_path)
try:
shutil.copytree(local_testdir, webdav_path)
yield webdav_url
finally:
shutil.rmtree(webdav_path, ignore_errors=True)


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -388,8 +393,9 @@ def docker_azurite(azurite_credentials):

@pytest.fixture(scope="session")
def azure_fixture(azurite_credentials, docker_azurite):
azure_storage = pytest.importorskip("azure.storage.blob")
account_name, connection_string = azurite_credentials
client = BlobServiceClient.from_connection_string(
client = azure_storage.BlobServiceClient.from_connection_string(
conn_str=connection_string
)
container_name = "data"
Expand Down