Skip to content

Commit

Permalink
implement SMBPath and tests (#219)
Browse files Browse the repository at this point in the history
* implement SMBPath and tests

* upath._flavour_sources: revert formatting changes

* tests: add smb protocol to registry tests

* tests: update smb glob test (mark '*' as xfail for now)

* upath.implementations.smb: configure flavour to correctly handle path parsing

* tests: skip smb test on windows

* upath.implementations.smb: make rename work with older fsspec

---------

Co-authored-by: fkuehnlenz <[email protected]>
Co-authored-by: Andreas Poehlmann <[email protected]>
  • Loading branch information
3 people committed Jun 14, 2024
1 parent 68d5707 commit d412c63
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 0 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dev =
# pyarrow
pydantic
pydantic-settings
smbprotocol

[options.package_data]
upath =
Expand Down
1 change: 1 addition & 0 deletions upath/_flavour.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class WrappedFileSystemFlavour: # (pathlib_abc.FlavourBase)
"https",
"s3",
"s3a",
"smb",
"gs",
"gcs",
"az",
Expand Down
52 changes: 52 additions & 0 deletions upath/implementations/smb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import warnings

import smbprotocol.exceptions

from upath import UPath


class SMBPath(UPath):
__slots__ = ()

def mkdir(self, mode=0o777, parents=False, exist_ok=False):
# smbclient does not support setting mode externally
if parents and not exist_ok and self.exists():
raise FileExistsError(str(self))
try:
self.fs.mkdir(
self.path,
create_parents=parents,
)
except smbprotocol.exceptions.SMBOSError:
if not exist_ok:
raise FileExistsError(str(self))
if not self.is_dir():
raise FileExistsError(str(self))

def iterdir(self):
if not self.is_dir():
raise NotADirectoryError(str(self))
else:
return super().iterdir()

def rename(self, target, **kwargs):
if "recursive" in kwargs:
warnings.warn(
"SMBPath.rename(): recursive is currently ignored.",
UserWarning,
stacklevel=2,
)
if "maxdepth" in kwargs:
warnings.warn(
"SMBPath.rename(): maxdepth is currently ignored.",
UserWarning,
stacklevel=2,
)
if not isinstance(target, UPath):
target = self.parent.joinpath(target).resolve()
self.fs.mv(
self.path,
target.path,
**kwargs,
)
return target
1 change: 1 addition & 0 deletions upath/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class _Registry(MutableMapping[str, "type[upath.UPath]"]):
"webdav+http": "upath.implementations.webdav.WebdavPath",
"webdav+https": "upath.implementations.webdav.WebdavPath",
"github": "upath.implementations.github.GitHubPath",
"smb": "upath.implementations.smb.SMBPath",
}

if TYPE_CHECKING:
Expand Down
55 changes: 55 additions & 0 deletions upath/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pytest
from fsspec.implementations.local import LocalFileSystem
from fsspec.implementations.local import make_path_posix
from fsspec.implementations.smb import SMBFileSystem
from fsspec.registry import _registry
from fsspec.registry import register_implementation
from fsspec.utils import stringify_path
Expand Down Expand Up @@ -409,3 +410,57 @@ def azure_fixture(azurite_credentials, azure_container):
finally:
for blob in client.list_blobs():
client.delete_blob(blob["name"])


@pytest.fixture(scope="module")
def smb_container():
try:
pchk = ["docker", "run", "--name", "fsspec_test_smb", "hello-world"]
subprocess.check_call(pchk)
stop_docker("fsspec_test_smb")
except (subprocess.CalledProcessError, FileNotFoundError):
pytest.skip("docker run not available")

# requires docker
container = "fsspec_smb"
stop_docker(container)
cfg = "-p -u 'testuser;testpass' -s 'home;/share;no;no;no;testuser'"
port = 445
img = f"docker run --name {container} --detach -p 139:139 -p {port}:445 dperson/samba" # noqa: E231 E501
cmd = f"{img} {cfg}"
try:
subprocess.check_output(shlex.split(cmd)).strip().decode()
time.sleep(2)
yield {
"host": "localhost",
"port": port,
"username": "testuser",
"password": "testpass",
"register_session_retries": 100, # max ~= 10 seconds
}
finally:
import smbclient # pylint: disable=import-outside-toplevel

smbclient.reset_connection_cache()
stop_docker(container)


@pytest.fixture
def smb_url(smb_container):
smb_url = "smb:https://{username}:{password}@{host}/home/"
smb_url = smb_url.format(**smb_container)
return smb_url


@pytest.fixture
def smb_fixture(local_testdir, smb_url, smb_container):
smb = SMBFileSystem(
host=smb_container["host"],
port=smb_container["port"],
username=smb_container["username"],
password=smb_container["password"],
)
url = smb_url + "testdir/"
smb.put(local_testdir, "/home/testdir", recursive=True)
yield url
smb.delete("/home/testdir", recursive=True)
38 changes: 38 additions & 0 deletions upath/tests/implementations/test_smb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
from fsspec import __version__ as fsspec_version
from packaging.version import Version

from upath import UPath
from upath.tests.cases import BaseTests
from upath.tests.utils import skip_on_windows


@skip_on_windows
class TestUPathSMB(BaseTests):

@pytest.fixture(autouse=True)
def path(self, smb_fixture):
self.path = UPath(smb_fixture)

@pytest.mark.parametrize(
"pattern",
(
"*.txt",
pytest.param(
"*",
marks=pytest.mark.xfail(
reason="SMBFileSystem.info appends '/' to dirs"
),
),
pytest.param(
"**/*.txt",
marks=(
pytest.mark.xfail(reason="requires fsspec>=2023.9.0")
if Version(fsspec_version) < Version("2023.9.0")
else ()
),
),
),
)
def test_glob(self, pathlib_base, pattern):
super().test_glob(pathlib_base, pattern)
1 change: 1 addition & 0 deletions upath/tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"memory",
"s3",
"s3a",
"smb",
"webdav",
"webdav+http",
"webdav+https",
Expand Down

0 comments on commit d412c63

Please sign in to comment.