1286 lines
37 KiB
Python
1286 lines
37 KiB
Python
|
import bz2
|
||
|
import gzip
|
||
|
import os
|
||
|
import os.path
|
||
|
import pickle
|
||
|
import posixpath
|
||
|
import sys
|
||
|
import tempfile
|
||
|
from contextlib import contextmanager
|
||
|
from pathlib import Path
|
||
|
from unittest.mock import patch
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
import fsspec
|
||
|
from fsspec import compression
|
||
|
from fsspec.core import OpenFile, get_fs_token_paths, open_files
|
||
|
from fsspec.implementations.local import LocalFileSystem, make_path_posix
|
||
|
from fsspec.tests.test_utils import WIN
|
||
|
|
||
|
files = {
|
||
|
".test.accounts.1.json": (
|
||
|
b'{"amount": 100, "name": "Alice"}\n'
|
||
|
b'{"amount": 200, "name": "Bob"}\n'
|
||
|
b'{"amount": 300, "name": "Charlie"}\n'
|
||
|
b'{"amount": 400, "name": "Dennis"}\n'
|
||
|
),
|
||
|
".test.accounts.2.json": (
|
||
|
b'{"amount": 500, "name": "Alice"}\n'
|
||
|
b'{"amount": 600, "name": "Bob"}\n'
|
||
|
b'{"amount": 700, "name": "Charlie"}\n'
|
||
|
b'{"amount": 800, "name": "Dennis"}\n'
|
||
|
),
|
||
|
}
|
||
|
|
||
|
csv_files = {
|
||
|
".test.fakedata.1.csv": (b"a,b\n1,2\n"),
|
||
|
".test.fakedata.2.csv": (b"a,b\n3,4\n"),
|
||
|
}
|
||
|
odir = os.getcwd()
|
||
|
|
||
|
|
||
|
@pytest.fixture()
|
||
|
def cwd():
|
||
|
pth = os.getcwd().replace("\\", "/")
|
||
|
assert not pth.endswith("/")
|
||
|
yield pth
|
||
|
|
||
|
|
||
|
@pytest.fixture()
|
||
|
def current_drive(cwd):
|
||
|
drive = os.path.splitdrive(cwd)[0]
|
||
|
assert not drive or (len(drive) == 2 and drive.endswith(":"))
|
||
|
yield drive
|
||
|
|
||
|
|
||
|
@pytest.fixture()
|
||
|
def user_home():
|
||
|
pth = os.path.expanduser("~").replace("\\", "/")
|
||
|
assert not pth.endswith("/")
|
||
|
yield pth
|
||
|
|
||
|
|
||
|
def winonly(*args):
|
||
|
return pytest.param(*args, marks=pytest.mark.skipif(not WIN, reason="Windows only"))
|
||
|
|
||
|
|
||
|
def posixonly(*args):
|
||
|
return pytest.param(*args, marks=pytest.mark.skipif(WIN, reason="Posix only"))
|
||
|
|
||
|
|
||
|
@contextmanager
|
||
|
def filetexts(d, open=open, mode="t"):
|
||
|
"""Dumps a number of textfiles to disk
|
||
|
|
||
|
d - dict
|
||
|
a mapping from filename to text like {'a.csv': '1,1\n2,2'}
|
||
|
|
||
|
Since this is meant for use in tests, this context manager will
|
||
|
automatically switch to a temporary current directory, to avoid
|
||
|
race conditions when running tests in parallel.
|
||
|
"""
|
||
|
dirname = tempfile.mkdtemp()
|
||
|
try:
|
||
|
os.chdir(dirname)
|
||
|
for filename, text in d.items():
|
||
|
if dirname := os.path.dirname(filename):
|
||
|
os.makedirs(dirname, exist_ok=True)
|
||
|
f = open(filename, f"w{mode}")
|
||
|
try:
|
||
|
f.write(text)
|
||
|
finally:
|
||
|
try:
|
||
|
f.close()
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
|
||
|
yield list(d)
|
||
|
|
||
|
for filename in d:
|
||
|
if os.path.exists(filename):
|
||
|
try:
|
||
|
os.remove(filename)
|
||
|
except OSError:
|
||
|
pass
|
||
|
finally:
|
||
|
os.chdir(odir)
|
||
|
|
||
|
|
||
|
def test_urlpath_inference_strips_protocol(tmpdir):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
paths = ["/".join([tmpdir, f"test.{i:02d}.csv"]) for i in range(20)]
|
||
|
|
||
|
for path in paths:
|
||
|
with open(path, "wb") as f:
|
||
|
f.write(b"1,2,3\n" * 10)
|
||
|
|
||
|
# globstring
|
||
|
protocol = "file:///" if sys.platform == "win32" else "file://"
|
||
|
urlpath = protocol + os.path.join(tmpdir, "test.*.csv")
|
||
|
_, _, paths2 = get_fs_token_paths(urlpath)
|
||
|
assert paths2 == paths
|
||
|
|
||
|
# list of paths
|
||
|
_, _, paths2 = get_fs_token_paths([protocol + p for p in paths])
|
||
|
assert paths2 == paths
|
||
|
|
||
|
|
||
|
def test_urlpath_inference_errors():
|
||
|
# Empty list
|
||
|
with pytest.raises(ValueError) as err:
|
||
|
get_fs_token_paths([])
|
||
|
assert "empty" in str(err.value)
|
||
|
|
||
|
pytest.importorskip("s3fs")
|
||
|
# Protocols differ
|
||
|
with pytest.raises(ValueError) as err:
|
||
|
get_fs_token_paths(["s3://test/path.csv", "/other/path.csv"])
|
||
|
assert "Protocol" in str(err.value)
|
||
|
|
||
|
|
||
|
def test_urlpath_expand_read():
|
||
|
"""Make sure * is expanded in file paths when reading."""
|
||
|
# when reading, globs should be expanded to read files by mask
|
||
|
with filetexts(csv_files, mode="b"):
|
||
|
_, _, paths = get_fs_token_paths("./.*.csv")
|
||
|
assert len(paths) == 2
|
||
|
_, _, paths = get_fs_token_paths(["./.*.csv"])
|
||
|
assert len(paths) == 2
|
||
|
|
||
|
|
||
|
def test_cats():
|
||
|
with filetexts(csv_files, mode="b"):
|
||
|
fs = fsspec.filesystem("file")
|
||
|
assert fs.cat(".test.fakedata.1.csv") == b"a,b\n1,2\n"
|
||
|
out = set(fs.cat([".test.fakedata.1.csv", ".test.fakedata.2.csv"]).values())
|
||
|
assert out == {b"a,b\n1,2\n", b"a,b\n3,4\n"}
|
||
|
assert fs.cat(".test.fakedata.1.csv", None, None) == b"a,b\n1,2\n"
|
||
|
assert fs.cat(".test.fakedata.1.csv", start=1, end=6) == b"a,b\n1,2\n"[1:6]
|
||
|
assert fs.cat(".test.fakedata.1.csv", start=-1) == b"a,b\n1,2\n"[-1:]
|
||
|
assert fs.cat(".test.fakedata.1.csv", start=1, end=-2) == b"a,b\n1,2\n"[1:-2]
|
||
|
out = set(
|
||
|
fs.cat(
|
||
|
[".test.fakedata.1.csv", ".test.fakedata.2.csv"], start=1, end=-1
|
||
|
).values()
|
||
|
)
|
||
|
assert out == {b"a,b\n1,2\n"[1:-1], b"a,b\n3,4\n"[1:-1]}
|
||
|
|
||
|
|
||
|
def test_urlpath_expand_write():
|
||
|
"""Make sure * is expanded in file paths when writing."""
|
||
|
_, _, paths = get_fs_token_paths("prefix-*.csv", mode="wb", num=2)
|
||
|
assert all(
|
||
|
p.endswith(pa) for p, pa in zip(paths, ["/prefix-0.csv", "/prefix-1.csv"])
|
||
|
)
|
||
|
_, _, paths = get_fs_token_paths(["prefix-*.csv"], mode="wb", num=2)
|
||
|
assert all(
|
||
|
p.endswith(pa) for p, pa in zip(paths, ["/prefix-0.csv", "/prefix-1.csv"])
|
||
|
)
|
||
|
# we can read with multiple masks, but not write
|
||
|
with pytest.raises(ValueError):
|
||
|
_, _, paths = get_fs_token_paths(
|
||
|
["prefix1-*.csv", "prefix2-*.csv"], mode="wb", num=2
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_open_files():
|
||
|
with filetexts(files, mode="b"):
|
||
|
myfiles = open_files("./.test.accounts.*")
|
||
|
assert len(myfiles) == len(files)
|
||
|
for lazy_file, data_file in zip(myfiles, sorted(files)):
|
||
|
with lazy_file as f:
|
||
|
x = f.read()
|
||
|
assert x == files[data_file]
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("encoding", ["utf-8", "ascii"])
|
||
|
def test_open_files_text_mode(encoding):
|
||
|
with filetexts(files, mode="b"):
|
||
|
myfiles = open_files("./.test.accounts.*", mode="rt", encoding=encoding)
|
||
|
assert len(myfiles) == len(files)
|
||
|
data = []
|
||
|
for file in myfiles:
|
||
|
with file as f:
|
||
|
data.append(f.read())
|
||
|
assert list(data) == [files[k].decode(encoding) for k in sorted(files)]
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("mode", ["rt", "rb"])
|
||
|
@pytest.mark.parametrize("fmt", list(compression.compr))
|
||
|
def test_compressions(fmt, mode, tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
fn = os.path.join(tmpdir, ".tmp.getsize")
|
||
|
fs = LocalFileSystem()
|
||
|
f = OpenFile(fs, fn, compression=fmt, mode="wb")
|
||
|
data = b"Long line of readily compressible text"
|
||
|
with f as fo:
|
||
|
fo.write(data)
|
||
|
if fmt is None:
|
||
|
assert fs.size(fn) == len(data)
|
||
|
else:
|
||
|
assert fs.size(fn) != len(data)
|
||
|
|
||
|
f = OpenFile(fs, fn, compression=fmt, mode=mode)
|
||
|
with f as fo:
|
||
|
if mode == "rb":
|
||
|
assert fo.read() == data
|
||
|
else:
|
||
|
assert fo.read() == data.decode()
|
||
|
|
||
|
|
||
|
def test_bad_compression():
|
||
|
with filetexts(files, mode="b"):
|
||
|
for func in [open_files]:
|
||
|
with pytest.raises(ValueError):
|
||
|
func("./.test.accounts.*", compression="not-found")
|
||
|
|
||
|
|
||
|
def test_not_found():
|
||
|
fn = "not-a-file"
|
||
|
fs = LocalFileSystem()
|
||
|
with pytest.raises((FileNotFoundError, OSError)):
|
||
|
with OpenFile(fs, fn, mode="rb"):
|
||
|
pass
|
||
|
|
||
|
|
||
|
def test_isfile():
|
||
|
fs = LocalFileSystem()
|
||
|
with filetexts(files, mode="b"):
|
||
|
for f in files.keys():
|
||
|
assert fs.isfile(f)
|
||
|
assert fs.isfile(f"file://{f}")
|
||
|
assert not fs.isfile("not-a-file")
|
||
|
assert not fs.isfile("file://not-a-file")
|
||
|
|
||
|
|
||
|
def test_isdir():
|
||
|
fs = LocalFileSystem()
|
||
|
with filetexts(files, mode="b"):
|
||
|
for f in files.keys():
|
||
|
assert fs.isdir(os.path.dirname(os.path.abspath(f)))
|
||
|
assert not fs.isdir(f)
|
||
|
assert not fs.isdir("not-a-dir")
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("compression_opener", [(None, open), ("gzip", gzip.open)])
|
||
|
def test_open_files_write(tmpdir, compression_opener):
|
||
|
tmpdir = str(tmpdir)
|
||
|
compression, opener = compression_opener
|
||
|
fn = str(tmpdir) + "/*.part"
|
||
|
files = open_files(fn, num=2, mode="wb", compression=compression)
|
||
|
assert len(files) == 2
|
||
|
assert {f.mode for f in files} == {"wb"}
|
||
|
for fil in files:
|
||
|
with fil as f:
|
||
|
f.write(b"000")
|
||
|
files = sorted(os.listdir(tmpdir))
|
||
|
assert files == ["0.part", "1.part"]
|
||
|
|
||
|
with opener(os.path.join(tmpdir, files[0]), "rb") as f:
|
||
|
d = f.read()
|
||
|
assert d == b"000"
|
||
|
|
||
|
|
||
|
def test_pickability_of_lazy_files(tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
cloudpickle = pytest.importorskip("cloudpickle")
|
||
|
|
||
|
with filetexts(files, mode="b"):
|
||
|
myfiles = open_files("./.test.accounts.*")
|
||
|
myfiles2 = cloudpickle.loads(cloudpickle.dumps(myfiles))
|
||
|
|
||
|
for f, f2 in zip(myfiles, myfiles2):
|
||
|
assert f.path == f2.path
|
||
|
assert isinstance(f.fs, type(f2.fs))
|
||
|
with f as f_open, f2 as f2_open:
|
||
|
assert f_open.read() == f2_open.read()
|
||
|
|
||
|
|
||
|
def test_abs_paths(tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
here = os.getcwd()
|
||
|
os.chdir(tmpdir)
|
||
|
with open("tmp", "w") as f:
|
||
|
f.write("hi")
|
||
|
out = LocalFileSystem().glob("./*")
|
||
|
assert len(out) == 1
|
||
|
assert "/" in out[0]
|
||
|
assert "tmp" in out[0]
|
||
|
|
||
|
# I don't know what this was testing - but should avoid local paths anyway
|
||
|
# fs = LocalFileSystem()
|
||
|
os.chdir(here)
|
||
|
# with fs.open('tmp', 'r') as f:
|
||
|
# res = f.read()
|
||
|
# assert res == 'hi'
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("sep", ["/", "\\"])
|
||
|
@pytest.mark.parametrize("chars", ["+", "++", "(", ")", "|", "\\"])
|
||
|
def test_glob_weird_characters(tmpdir, sep, chars):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
|
||
|
subdir = f"{tmpdir}{sep}test{chars}x"
|
||
|
try:
|
||
|
os.makedirs(subdir, exist_ok=True)
|
||
|
except OSError as e:
|
||
|
if WIN and "label syntax" in str(e):
|
||
|
pytest.xfail("Illegal windows directory name")
|
||
|
else:
|
||
|
raise
|
||
|
with open(subdir + sep + "tmp", "w") as f:
|
||
|
f.write("hi")
|
||
|
|
||
|
out = LocalFileSystem().glob(subdir + sep + "*")
|
||
|
assert len(out) == 1
|
||
|
assert "/" in out[0]
|
||
|
assert "tmp" in out[0]
|
||
|
|
||
|
|
||
|
def test_globfind_dirs(tmpdir):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
fs = fsspec.filesystem("file")
|
||
|
fs.mkdir(tmpdir + "/dir")
|
||
|
fs.touch(tmpdir + "/dir/afile")
|
||
|
assert [tmpdir + "/dir"] == fs.glob(tmpdir + "/*")
|
||
|
assert fs.glob(tmpdir + "/*", detail=True)[tmpdir + "/dir"]["type"] == "directory"
|
||
|
assert (
|
||
|
fs.glob(tmpdir + "/dir/*", detail=True)[tmpdir + "/dir/afile"]["type"] == "file"
|
||
|
)
|
||
|
assert [tmpdir + "/dir/afile"] == fs.find(tmpdir)
|
||
|
assert [tmpdir, tmpdir + "/dir", tmpdir + "/dir/afile"] == fs.find(
|
||
|
tmpdir, withdirs=True
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_touch(tmpdir):
|
||
|
import time
|
||
|
|
||
|
fn = str(tmpdir + "/in/file")
|
||
|
fs = fsspec.filesystem("file", auto_mkdir=False)
|
||
|
with pytest.raises(OSError):
|
||
|
fs.touch(fn)
|
||
|
fs = fsspec.filesystem("file", auto_mkdir=True)
|
||
|
fs.touch(fn)
|
||
|
info = fs.info(fn)
|
||
|
time.sleep(0.2)
|
||
|
fs.touch(fn)
|
||
|
info2 = fs.info(fn)
|
||
|
if not WIN:
|
||
|
assert info2["mtime"] > info["mtime"]
|
||
|
|
||
|
|
||
|
def test_touch_truncate(tmpdir):
|
||
|
fn = str(tmpdir + "/tfile")
|
||
|
fs = fsspec.filesystem("file")
|
||
|
fs.touch(fn, truncate=True)
|
||
|
fs.pipe(fn, b"a")
|
||
|
fs.touch(fn, truncate=True)
|
||
|
assert fs.cat(fn) == b""
|
||
|
fs.pipe(fn, b"a")
|
||
|
fs.touch(fn, truncate=False)
|
||
|
assert fs.cat(fn) == b"a"
|
||
|
|
||
|
|
||
|
def test_directories(tmpdir):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
fs = LocalFileSystem()
|
||
|
fs.mkdir(tmpdir + "/dir")
|
||
|
assert tmpdir + "/dir" in fs.ls(tmpdir)
|
||
|
assert fs.ls(tmpdir, True)[0]["type"] == "directory"
|
||
|
fs.rmdir(tmpdir + "/dir")
|
||
|
assert not fs.ls(tmpdir)
|
||
|
assert fs.ls(fs.root_marker)
|
||
|
|
||
|
|
||
|
def test_ls_on_file(tmpdir):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
fs = LocalFileSystem()
|
||
|
resource = tmpdir + "/a.json"
|
||
|
fs.touch(resource)
|
||
|
assert fs.exists(resource)
|
||
|
assert fs.ls(tmpdir) == fs.ls(resource)
|
||
|
assert fs.ls(resource, detail=True)[0] == fs.info(resource)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("file_protocol", ["", "file://"])
|
||
|
def test_file_ops(tmpdir, file_protocol):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
tmpdir_with_protocol = file_protocol + tmpdir
|
||
|
fs = LocalFileSystem(auto_mkdir=True)
|
||
|
with pytest.raises(FileNotFoundError):
|
||
|
fs.info(tmpdir_with_protocol + "/nofile")
|
||
|
fs.touch(tmpdir_with_protocol + "/afile")
|
||
|
i1 = fs.ukey(tmpdir_with_protocol + "/afile")
|
||
|
|
||
|
assert tmpdir + "/afile" in fs.ls(tmpdir_with_protocol)
|
||
|
|
||
|
with fs.open(tmpdir_with_protocol + "/afile", "wb") as f:
|
||
|
f.write(b"data")
|
||
|
i2 = fs.ukey(tmpdir_with_protocol + "/afile")
|
||
|
assert i1 != i2 # because file changed
|
||
|
|
||
|
fs.copy(tmpdir_with_protocol + "/afile", tmpdir_with_protocol + "/afile2")
|
||
|
assert tmpdir + "/afile2" in fs.ls(tmpdir_with_protocol)
|
||
|
|
||
|
fs.move(tmpdir_with_protocol + "/afile", tmpdir_with_protocol + "/afile3")
|
||
|
assert not fs.exists(tmpdir_with_protocol + "/afile")
|
||
|
|
||
|
fs.cp(
|
||
|
tmpdir_with_protocol + "/afile3", tmpdir_with_protocol + "/deeply/nested/file"
|
||
|
)
|
||
|
assert fs.exists(tmpdir_with_protocol + "/deeply/nested/file")
|
||
|
|
||
|
fs.rm(tmpdir_with_protocol + "/afile3", recursive=True)
|
||
|
assert not fs.exists(tmpdir_with_protocol + "/afile3")
|
||
|
|
||
|
files = [tmpdir_with_protocol + "/afile4", tmpdir_with_protocol + "/afile5"]
|
||
|
[fs.touch(f) for f in files]
|
||
|
|
||
|
with pytest.raises(AttributeError):
|
||
|
fs.rm_file(files)
|
||
|
fs.rm(files)
|
||
|
assert all(not fs.exists(f) for f in files)
|
||
|
|
||
|
fs.touch(tmpdir_with_protocol + "/afile6")
|
||
|
fs.rm_file(tmpdir_with_protocol + "/afile6")
|
||
|
assert not fs.exists(tmpdir_with_protocol + "/afile6")
|
||
|
|
||
|
# IsADirectoryError raised on Linux, PermissionError on Windows
|
||
|
with pytest.raises((IsADirectoryError, PermissionError)):
|
||
|
fs.rm_file(tmpdir_with_protocol)
|
||
|
|
||
|
fs.rm(tmpdir_with_protocol, recursive=True)
|
||
|
assert not fs.exists(tmpdir_with_protocol)
|
||
|
|
||
|
|
||
|
def test_recursive_get_put(tmpdir):
|
||
|
tmpdir = make_path_posix(str(tmpdir))
|
||
|
fs = LocalFileSystem(auto_mkdir=True)
|
||
|
|
||
|
fs.mkdir(tmpdir + "/a1/a2/a3")
|
||
|
fs.touch(tmpdir + "/a1/a2/a3/afile")
|
||
|
fs.touch(tmpdir + "/a1/afile")
|
||
|
|
||
|
fs.get(f"file://{tmpdir}/a1", tmpdir + "/b1", recursive=True)
|
||
|
assert fs.isfile(tmpdir + "/b1/afile")
|
||
|
assert fs.isfile(tmpdir + "/b1/a2/a3/afile")
|
||
|
|
||
|
fs.put(tmpdir + "/b1", f"file://{tmpdir}/c1", recursive=True)
|
||
|
assert fs.isfile(tmpdir + "/c1/afile")
|
||
|
assert fs.isfile(tmpdir + "/c1/a2/a3/afile")
|
||
|
|
||
|
|
||
|
def test_commit_discard(tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
fs = LocalFileSystem()
|
||
|
with fs.transaction:
|
||
|
with fs.open(tmpdir + "/afile", "wb") as f:
|
||
|
assert not fs.exists(tmpdir + "/afile")
|
||
|
f.write(b"data")
|
||
|
assert not fs.exists(tmpdir + "/afile")
|
||
|
|
||
|
assert fs._transaction is None
|
||
|
assert fs.cat(tmpdir + "/afile") == b"data"
|
||
|
|
||
|
try:
|
||
|
with fs.transaction:
|
||
|
with fs.open(tmpdir + "/bfile", "wb") as f:
|
||
|
f.write(b"data")
|
||
|
raise KeyboardInterrupt
|
||
|
except KeyboardInterrupt:
|
||
|
assert not fs.exists(tmpdir + "/bfile")
|
||
|
|
||
|
|
||
|
def test_make_path_posix():
|
||
|
cwd = os.getcwd()
|
||
|
if WIN:
|
||
|
drive = cwd[0]
|
||
|
assert make_path_posix("/a/posix/path") == f"{drive}:/a/posix/path"
|
||
|
assert make_path_posix("/posix") == f"{drive}:/posix"
|
||
|
# Windows drive requires trailing slash
|
||
|
assert make_path_posix("C:\\") == "C:/"
|
||
|
else:
|
||
|
assert make_path_posix("/a/posix/path") == "/a/posix/path"
|
||
|
assert make_path_posix("/posix") == "/posix"
|
||
|
assert make_path_posix("relpath") == posixpath.join(make_path_posix(cwd), "relpath")
|
||
|
assert make_path_posix("rel/path") == posixpath.join(
|
||
|
make_path_posix(cwd), "rel/path"
|
||
|
)
|
||
|
# NT style
|
||
|
if WIN:
|
||
|
assert make_path_posix("C:\\path") == "C:/path"
|
||
|
assert (
|
||
|
make_path_posix(
|
||
|
"\\\\windows-server\\someshare\\path\\more\\path\\dir\\foo.parquet",
|
||
|
)
|
||
|
== "//windows-server/someshare/path/more/path/dir/foo.parquet"
|
||
|
)
|
||
|
assert (
|
||
|
make_path_posix(
|
||
|
"\\\\SERVER\\UserHomeFolder$\\me\\My Documents\\proj\\data\\fname.csv",
|
||
|
)
|
||
|
== "//SERVER/UserHomeFolder$/me/My Documents/proj/data/fname.csv"
|
||
|
)
|
||
|
assert "/" in make_path_posix("rel\\path")
|
||
|
# Relative
|
||
|
pp = make_path_posix("./path")
|
||
|
cd = make_path_posix(cwd)
|
||
|
assert pp == cd + "/path"
|
||
|
# Userpath
|
||
|
userpath = make_path_posix("~/path")
|
||
|
assert userpath.endswith("/path")
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"path",
|
||
|
[
|
||
|
"/abc/def",
|
||
|
"abc/def",
|
||
|
"",
|
||
|
".",
|
||
|
"//server/share/",
|
||
|
"\\\\server\\share\\",
|
||
|
"C:\\",
|
||
|
"d:/abc/def",
|
||
|
"e:",
|
||
|
pytest.param(
|
||
|
"\\\\server\\share",
|
||
|
marks=[
|
||
|
pytest.mark.xfail(
|
||
|
WIN and sys.version_info < (3, 11),
|
||
|
reason="requires py3.11+ see: python/cpython#96290",
|
||
|
)
|
||
|
],
|
||
|
),
|
||
|
pytest.param(
|
||
|
"f:foo",
|
||
|
marks=[pytest.mark.xfail(WIN, reason="unsupported")],
|
||
|
id="relative-path-with-drive",
|
||
|
),
|
||
|
],
|
||
|
)
|
||
|
def test_make_path_posix_returns_absolute_paths(path):
|
||
|
posix_pth = make_path_posix(path)
|
||
|
assert os.path.isabs(posix_pth)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("container_cls", [list, set, tuple])
|
||
|
def test_make_path_posix_set_list_tuple(container_cls):
|
||
|
paths = container_cls(
|
||
|
[
|
||
|
"/foo/bar",
|
||
|
"bar/foo",
|
||
|
]
|
||
|
)
|
||
|
posix_paths = make_path_posix(paths)
|
||
|
assert isinstance(posix_paths, container_cls)
|
||
|
assert posix_paths == container_cls(
|
||
|
[
|
||
|
make_path_posix("/foo/bar"),
|
||
|
make_path_posix("bar/foo"),
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"obj",
|
||
|
[
|
||
|
1,
|
||
|
True,
|
||
|
None,
|
||
|
object(),
|
||
|
],
|
||
|
)
|
||
|
def test_make_path_posix_wrong_type(obj):
|
||
|
with pytest.raises(TypeError):
|
||
|
make_path_posix(obj)
|
||
|
|
||
|
|
||
|
def test_parent():
|
||
|
if WIN:
|
||
|
assert LocalFileSystem._parent("C:\\file or folder") == "C:/"
|
||
|
assert LocalFileSystem._parent("C:\\") == "C:/"
|
||
|
else:
|
||
|
assert LocalFileSystem._parent("/file or folder") == "/"
|
||
|
assert LocalFileSystem._parent("/") == "/"
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"path,parent",
|
||
|
[
|
||
|
("C:\\", "C:/"),
|
||
|
("C:\\.", "C:/"),
|
||
|
("C:\\.\\", "C:/"),
|
||
|
("file:C:/", "C:/"),
|
||
|
("file://C:/", "C:/"),
|
||
|
("local:C:/", "C:/"),
|
||
|
("local://C:/", "C:/"),
|
||
|
("\\\\server\\share", "//server/share"),
|
||
|
("\\\\server\\share\\", "//server/share"),
|
||
|
("\\\\server\\share\\path", "//server/share"),
|
||
|
("//server/share", "//server/share"),
|
||
|
("//server/share/", "//server/share"),
|
||
|
("//server/share/path", "//server/share"),
|
||
|
("C:\\file or folder", "C:/"),
|
||
|
("C:\\file or folder\\", "C:/"),
|
||
|
("file:///", "{current_drive}/"),
|
||
|
("file:///path", "{current_drive}/"),
|
||
|
]
|
||
|
if WIN
|
||
|
else [
|
||
|
("/", "/"),
|
||
|
("/.", "/"),
|
||
|
("/./", "/"),
|
||
|
("file:/", "/"),
|
||
|
("file:///", "/"),
|
||
|
("local:/", "/"),
|
||
|
("local:///", "/"),
|
||
|
("/file or folder", "/"),
|
||
|
("/file or folder/", "/"),
|
||
|
("file:///path", "/"),
|
||
|
("file://c/", "{cwd}"),
|
||
|
],
|
||
|
)
|
||
|
def test_parent_edge_cases(path, parent, cwd, current_drive):
|
||
|
parent = parent.format(cwd=cwd, current_drive=current_drive)
|
||
|
|
||
|
assert LocalFileSystem._parent(path) == parent
|
||
|
|
||
|
|
||
|
def test_linked_files(tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
fn0 = os.path.join(tmpdir, "target")
|
||
|
fn1 = os.path.join(tmpdir, "link1")
|
||
|
fn2 = os.path.join(tmpdir, "link2")
|
||
|
data = b"my target data"
|
||
|
with open(fn0, "wb") as f:
|
||
|
f.write(data)
|
||
|
try:
|
||
|
os.symlink(fn0, fn1)
|
||
|
os.symlink(fn0, fn2)
|
||
|
except OSError:
|
||
|
if WIN:
|
||
|
pytest.xfail("Ran on win without admin permissions")
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
assert fs.info(fn0)["type"] == "file"
|
||
|
assert fs.info(fn1)["type"] == "file"
|
||
|
assert fs.info(fn2)["type"] == "file"
|
||
|
|
||
|
assert not fs.info(fn0)["islink"]
|
||
|
assert fs.info(fn1)["islink"]
|
||
|
assert fs.info(fn2)["islink"]
|
||
|
|
||
|
assert fs.info(fn0)["size"] == len(data)
|
||
|
assert fs.info(fn1)["size"] == len(data)
|
||
|
assert fs.info(fn2)["size"] == len(data)
|
||
|
|
||
|
of = fsspec.open(fn1, "rb")
|
||
|
with of as f:
|
||
|
assert f.read() == data
|
||
|
|
||
|
of = fsspec.open(fn2, "rb")
|
||
|
with of as f:
|
||
|
assert f.read() == data
|
||
|
|
||
|
|
||
|
def test_linked_files_exists(tmpdir):
|
||
|
origin = tmpdir / "original"
|
||
|
copy_file = tmpdir / "copy"
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
fs.touch(origin)
|
||
|
|
||
|
try:
|
||
|
os.symlink(origin, copy_file)
|
||
|
except OSError:
|
||
|
if WIN:
|
||
|
pytest.xfail("Ran on win without admin permissions")
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
assert fs.exists(copy_file)
|
||
|
assert fs.lexists(copy_file)
|
||
|
|
||
|
os.unlink(origin)
|
||
|
|
||
|
assert not fs.exists(copy_file)
|
||
|
assert fs.lexists(copy_file)
|
||
|
|
||
|
os.unlink(copy_file)
|
||
|
|
||
|
assert not fs.exists(copy_file)
|
||
|
assert not fs.lexists(copy_file)
|
||
|
|
||
|
|
||
|
def test_linked_directories(tmpdir):
|
||
|
tmpdir = str(tmpdir)
|
||
|
|
||
|
subdir0 = os.path.join(tmpdir, "target")
|
||
|
subdir1 = os.path.join(tmpdir, "link1")
|
||
|
subdir2 = os.path.join(tmpdir, "link2")
|
||
|
|
||
|
os.makedirs(subdir0)
|
||
|
|
||
|
try:
|
||
|
os.symlink(subdir0, subdir1)
|
||
|
os.symlink(subdir0, subdir2)
|
||
|
except OSError:
|
||
|
if WIN:
|
||
|
pytest.xfail("Ran on win without admin permissions")
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
assert fs.info(subdir0)["type"] == "directory"
|
||
|
assert fs.info(subdir1)["type"] == "directory"
|
||
|
assert fs.info(subdir2)["type"] == "directory"
|
||
|
|
||
|
assert not fs.info(subdir0)["islink"]
|
||
|
assert fs.info(subdir1)["islink"]
|
||
|
assert fs.info(subdir2)["islink"]
|
||
|
|
||
|
|
||
|
def test_isfilestore():
|
||
|
fs = LocalFileSystem(auto_mkdir=False)
|
||
|
assert fs._isfilestore()
|
||
|
|
||
|
|
||
|
def test_pickle(tmpdir):
|
||
|
fs = LocalFileSystem()
|
||
|
tmpdir = str(tmpdir)
|
||
|
fn0 = os.path.join(tmpdir, "target")
|
||
|
|
||
|
with open(fn0, "wb") as f:
|
||
|
f.write(b"data")
|
||
|
|
||
|
f = fs.open(fn0, "rb")
|
||
|
f.seek(1)
|
||
|
f2 = pickle.loads(pickle.dumps(f))
|
||
|
assert f2.read() == f.read()
|
||
|
|
||
|
f = fs.open(fn0, "wb")
|
||
|
with pytest.raises(ValueError):
|
||
|
pickle.dumps(f)
|
||
|
|
||
|
# with context
|
||
|
with fs.open(fn0, "rb") as f:
|
||
|
f.seek(1)
|
||
|
f2 = pickle.loads(pickle.dumps(f))
|
||
|
assert f2.tell() == 1
|
||
|
assert f2.read() == f.read()
|
||
|
|
||
|
# with fsspec.open https://github.com/fsspec/filesystem_spec/issues/579
|
||
|
with fsspec.open(fn0, "rb") as f:
|
||
|
f.seek(1)
|
||
|
f2 = pickle.loads(pickle.dumps(f))
|
||
|
assert f2.tell() == 1
|
||
|
assert f2.read() == f.read()
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, expected",
|
||
|
[
|
||
|
("file://~/foo/bar", "{user_home}/foo/bar"),
|
||
|
("~/foo/bar", "{user_home}/foo/bar"),
|
||
|
winonly("~\\foo\\bar", "{user_home}/foo/bar"),
|
||
|
winonly("file://~\\foo\\bar", "{user_home}/foo/bar"),
|
||
|
],
|
||
|
)
|
||
|
def test_strip_protocol_expanduser(uri, expected, user_home):
|
||
|
expected = expected.format(user_home=user_home)
|
||
|
|
||
|
stripped = LocalFileSystem._strip_protocol(uri)
|
||
|
assert expected == stripped
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, expected",
|
||
|
[
|
||
|
("file://", "{cwd}"),
|
||
|
("file://.", "{cwd}"),
|
||
|
("file://./", "{cwd}"),
|
||
|
("./", "{cwd}"),
|
||
|
("file:path", "{cwd}/path"),
|
||
|
("file://path", "{cwd}/path"),
|
||
|
("path", "{cwd}/path"),
|
||
|
("./path", "{cwd}/path"),
|
||
|
winonly(".\\", "{cwd}"),
|
||
|
winonly("file://.\\path", "{cwd}/path"),
|
||
|
],
|
||
|
)
|
||
|
def test_strip_protocol_relative_paths(uri, expected, cwd):
|
||
|
expected = expected.format(cwd=cwd)
|
||
|
|
||
|
stripped = LocalFileSystem._strip_protocol(uri)
|
||
|
assert expected == stripped
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, expected",
|
||
|
[
|
||
|
posixonly("file:/foo/bar", "/foo/bar"),
|
||
|
winonly("file:/foo/bar", "{current_drive}/foo/bar"),
|
||
|
winonly("file:\\foo\\bar", "{current_drive}/foo/bar"),
|
||
|
winonly("file:D:\\path\\file", "D:/path/file"),
|
||
|
winonly("file:/D:\\path\\file", "D:/path/file"),
|
||
|
winonly("file://D:\\path\\file", "D:/path/file"),
|
||
|
],
|
||
|
)
|
||
|
def test_strip_protocol_no_authority(uri, expected, cwd, current_drive):
|
||
|
expected = expected.format(cwd=cwd, current_drive=current_drive)
|
||
|
|
||
|
stripped = LocalFileSystem._strip_protocol(uri)
|
||
|
assert expected == stripped
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, expected",
|
||
|
[
|
||
|
("file:/path", "/path"),
|
||
|
("file:///path", "/path"),
|
||
|
("file:////path", "//path"),
|
||
|
("local:/path", "/path"),
|
||
|
("s3://bucket/key", "{cwd}/s3://bucket/key"),
|
||
|
("/path", "/path"),
|
||
|
("file:///", "/"),
|
||
|
]
|
||
|
if not WIN
|
||
|
else [
|
||
|
("file:c:/path", "c:/path"),
|
||
|
("file:/c:/path", "c:/path"),
|
||
|
("file:/C:/path", "C:/path"),
|
||
|
("file://c:/path", "c:/path"),
|
||
|
("file:///c:/path", "c:/path"),
|
||
|
("local:/path", "{current_drive}/path"),
|
||
|
("s3://bucket/key", "{cwd}/s3://bucket/key"),
|
||
|
("c:/path", "c:/path"),
|
||
|
("c:\\path", "c:/path"),
|
||
|
("file:///", "{current_drive}/"),
|
||
|
pytest.param(
|
||
|
"file://localhost/c:/path",
|
||
|
"c:/path",
|
||
|
marks=pytest.mark.xfail(
|
||
|
reason="rfc8089 section3 'localhost uri' not supported"
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
)
|
||
|
def test_strip_protocol_absolute_paths(uri, expected, current_drive, cwd):
|
||
|
expected = expected.format(current_drive=current_drive, cwd=cwd)
|
||
|
|
||
|
stripped = LocalFileSystem._strip_protocol(uri)
|
||
|
assert expected == stripped
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, expected",
|
||
|
[
|
||
|
("file:c|/path", "c:/path"),
|
||
|
("file:/D|/path", "D:/path"),
|
||
|
("file:///C|/path", "C:/path"),
|
||
|
],
|
||
|
)
|
||
|
@pytest.mark.skipif(not WIN, reason="Windows only")
|
||
|
@pytest.mark.xfail(WIN, reason="legacy dos uris not supported")
|
||
|
def test_strip_protocol_legacy_dos_uris(uri, expected):
|
||
|
stripped = LocalFileSystem._strip_protocol(uri)
|
||
|
assert expected == stripped
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"uri, stripped",
|
||
|
[
|
||
|
("file://remote/share/pth", "{cwd}/remote/share/pth"),
|
||
|
("file:////remote/share/pth", "//remote/share/pth"),
|
||
|
("file://///remote/share/pth", "///remote/share/pth"),
|
||
|
("//remote/share/pth", "//remote/share/pth"),
|
||
|
winonly("\\\\remote\\share\\pth", "//remote/share/pth"),
|
||
|
],
|
||
|
)
|
||
|
def test_strip_protocol_windows_remote_shares(uri, stripped, cwd):
|
||
|
stripped = stripped.format(cwd=cwd)
|
||
|
|
||
|
assert LocalFileSystem._strip_protocol(uri) == stripped
|
||
|
|
||
|
|
||
|
def test_mkdir_twice_faile(tmpdir):
|
||
|
fn = os.path.join(tmpdir, "test")
|
||
|
fs = fsspec.filesystem("file")
|
||
|
fs.mkdir(fn)
|
||
|
with pytest.raises(FileExistsError):
|
||
|
fs.mkdir(fn)
|
||
|
|
||
|
|
||
|
def test_iterable(tmpdir):
|
||
|
data = b"a\nhello\noi"
|
||
|
fn = os.path.join(tmpdir, "test")
|
||
|
with open(fn, "wb") as f:
|
||
|
f.write(data)
|
||
|
of = fsspec.open(f"file://{fn}", "rb")
|
||
|
with of as f:
|
||
|
out = list(f)
|
||
|
assert b"".join(out) == data
|
||
|
|
||
|
|
||
|
def test_mv_empty(tmpdir):
|
||
|
localfs = fsspec.filesystem("file")
|
||
|
src = os.path.join(str(tmpdir), "src")
|
||
|
dest = os.path.join(str(tmpdir), "dest")
|
||
|
assert localfs.isdir(src) is False
|
||
|
localfs.mkdir(src)
|
||
|
assert localfs.isdir(src)
|
||
|
localfs.move(src, dest, recursive=True)
|
||
|
assert localfs.isdir(src) is False
|
||
|
assert localfs.isdir(dest)
|
||
|
assert localfs.info(dest)
|
||
|
|
||
|
|
||
|
def test_mv_recursive(tmpdir):
|
||
|
localfs = fsspec.filesystem("file")
|
||
|
src = os.path.join(str(tmpdir), "src")
|
||
|
dest = os.path.join(str(tmpdir), "dest")
|
||
|
assert localfs.isdir(src) is False
|
||
|
localfs.mkdir(src)
|
||
|
assert localfs.isdir(src)
|
||
|
localfs.touch(os.path.join(src, "afile"))
|
||
|
localfs.move(src, dest, recursive=True)
|
||
|
assert localfs.isdir(src) is False
|
||
|
assert localfs.isdir(dest)
|
||
|
assert localfs.info(os.path.join(dest, "afile"))
|
||
|
|
||
|
|
||
|
@pytest.mark.xfail(WIN, reason="windows expand path to be revisited")
|
||
|
def test_copy_errors(tmpdir):
|
||
|
localfs = fsspec.filesystem("file", auto_mkdir=True)
|
||
|
|
||
|
dest1 = os.path.join(str(tmpdir), "dest1")
|
||
|
dest2 = os.path.join(str(tmpdir), "dest2")
|
||
|
|
||
|
src = os.path.join(str(tmpdir), "src")
|
||
|
file1 = os.path.join(src, "afile1")
|
||
|
file2 = os.path.join(src, "afile2")
|
||
|
dne = os.path.join(str(tmpdir), "src", "notafile")
|
||
|
|
||
|
localfs.mkdir(src)
|
||
|
localfs.mkdir(dest1)
|
||
|
localfs.mkdir(dest2)
|
||
|
localfs.touch(file1)
|
||
|
localfs.touch(file2)
|
||
|
|
||
|
# Non recursive should raise an error unless we specify ignore
|
||
|
with pytest.raises(FileNotFoundError):
|
||
|
localfs.copy([file1, file2, dne], dest1)
|
||
|
|
||
|
localfs.copy([file1, file2, dne], dest1, on_error="ignore")
|
||
|
|
||
|
assert sorted(localfs.ls(dest1)) == [
|
||
|
make_path_posix(os.path.join(dest1, "afile1")),
|
||
|
make_path_posix(os.path.join(dest1, "afile2")),
|
||
|
]
|
||
|
|
||
|
# Recursive should raise an error only if we specify raise
|
||
|
# the patch simulates the filesystem finding a file that does not
|
||
|
# exist in the directory
|
||
|
current_files = localfs.expand_path(src, recursive=True)
|
||
|
with patch.object(localfs, "expand_path", return_value=current_files + [dne]):
|
||
|
with pytest.raises(FileNotFoundError):
|
||
|
localfs.copy(src + "/", dest2, recursive=True, on_error="raise")
|
||
|
|
||
|
localfs.copy(src + "/", dest2, recursive=True)
|
||
|
assert sorted(localfs.ls(dest2)) == [
|
||
|
make_path_posix(os.path.join(dest2, "afile1")),
|
||
|
make_path_posix(os.path.join(dest2, "afile2")),
|
||
|
]
|
||
|
|
||
|
|
||
|
def test_transaction(tmpdir):
|
||
|
file = str(tmpdir / "test.txt")
|
||
|
fs = LocalFileSystem()
|
||
|
|
||
|
with fs.transaction:
|
||
|
content = "hello world"
|
||
|
with fs.open(file, "w") as fp:
|
||
|
fp.write(content)
|
||
|
|
||
|
with fs.open(file, "r") as fp:
|
||
|
read_content = fp.read()
|
||
|
|
||
|
assert content == read_content
|
||
|
|
||
|
|
||
|
def test_delete_cwd(tmpdir):
|
||
|
cwd = os.getcwd()
|
||
|
fs = LocalFileSystem()
|
||
|
try:
|
||
|
os.chdir(tmpdir)
|
||
|
with pytest.raises(ValueError):
|
||
|
fs.rm(".", recursive=True)
|
||
|
finally:
|
||
|
os.chdir(cwd)
|
||
|
|
||
|
|
||
|
def test_delete_non_recursive_dir_fails(tmpdir):
|
||
|
fs = LocalFileSystem()
|
||
|
subdir = os.path.join(tmpdir, "testdir")
|
||
|
fs.mkdir(subdir)
|
||
|
with pytest.raises(ValueError):
|
||
|
fs.rm(subdir)
|
||
|
fs.rm(subdir, recursive=True)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"opener, ext", [(bz2.open, ".bz2"), (gzip.open, ".gz"), (open, "")]
|
||
|
)
|
||
|
def test_infer_compression(tmpdir, opener, ext):
|
||
|
filename = str(tmpdir / f"test{ext}")
|
||
|
content = b"hello world"
|
||
|
with opener(filename, "wb") as fp:
|
||
|
fp.write(content)
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
with fs.open(f"file://{filename}", "rb", compression="infer") as fp:
|
||
|
read_content = fp.read()
|
||
|
|
||
|
assert content == read_content
|
||
|
|
||
|
|
||
|
def test_info_path_like(tmpdir):
|
||
|
path = Path(tmpdir / "test_info")
|
||
|
path.write_text("fsspec")
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
assert fs.exists(path)
|
||
|
|
||
|
|
||
|
def test_seekable(tmpdir):
|
||
|
fs = LocalFileSystem()
|
||
|
tmpdir = str(tmpdir)
|
||
|
fn0 = os.path.join(tmpdir, "target")
|
||
|
|
||
|
with open(fn0, "wb") as f:
|
||
|
f.write(b"data")
|
||
|
|
||
|
f = fs.open(fn0, "rt")
|
||
|
assert f.seekable(), "file is not seekable"
|
||
|
f.seek(1)
|
||
|
assert f.read(1) == "a"
|
||
|
assert f.tell() == 2
|
||
|
|
||
|
|
||
|
def test_numpy_fromfile(tmpdir):
|
||
|
# Regression test for #1005.
|
||
|
np = pytest.importorskip("numpy")
|
||
|
fn = str(tmpdir / "test_arr.npy")
|
||
|
dt = np.int64
|
||
|
arr = np.arange(10, dtype=dt)
|
||
|
arr.tofile(fn)
|
||
|
assert np.array_equal(np.fromfile(fn, dtype=dt), arr)
|
||
|
|
||
|
|
||
|
def test_link(tmpdir):
|
||
|
target = os.path.join(tmpdir, "target")
|
||
|
link = os.path.join(tmpdir, "link")
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
fs.touch(target)
|
||
|
|
||
|
fs.link(target, link)
|
||
|
assert fs.info(link)["nlink"] > 1
|
||
|
|
||
|
|
||
|
def test_symlink(tmpdir):
|
||
|
target = os.path.join(tmpdir, "target")
|
||
|
link = os.path.join(tmpdir, "link")
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
fs.touch(target)
|
||
|
try:
|
||
|
fs.symlink(target, link)
|
||
|
except OSError as e:
|
||
|
if "[WinError 1314]" in str(e):
|
||
|
# Windows requires developer mode to be enabled to use symbolic links
|
||
|
return
|
||
|
raise
|
||
|
assert fs.islink(link)
|
||
|
|
||
|
|
||
|
# https://github.com/fsspec/filesystem_spec/issues/967
|
||
|
def test_put_file_to_dir(tmpdir):
|
||
|
src_file = os.path.join(str(tmpdir), "src")
|
||
|
target_dir = os.path.join(str(tmpdir), "target")
|
||
|
target_file = os.path.join(target_dir, "src")
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
fs.touch(src_file)
|
||
|
fs.mkdir(target_dir)
|
||
|
fs.put(src_file, target_dir)
|
||
|
|
||
|
assert fs.isfile(target_file)
|
||
|
|
||
|
|
||
|
def test_du(tmpdir):
|
||
|
file = tmpdir / "file"
|
||
|
subdir = tmpdir / "subdir"
|
||
|
subfile = subdir / "subfile"
|
||
|
|
||
|
fs = LocalFileSystem()
|
||
|
with open(file, "wb") as f:
|
||
|
f.write(b"4444")
|
||
|
fs.mkdir(subdir)
|
||
|
with open(subfile, "wb") as f:
|
||
|
f.write(b"7777777")
|
||
|
|
||
|
# Switch to posix paths for comparisons
|
||
|
tmpdir_posix = Path(tmpdir).as_posix()
|
||
|
file_posix = Path(file).as_posix()
|
||
|
subdir_posix = Path(subdir).as_posix()
|
||
|
subfile_posix = Path(subfile).as_posix()
|
||
|
|
||
|
assert fs.du(tmpdir) == 11
|
||
|
assert fs.du(tmpdir, total=False) == {file_posix: 4, subfile_posix: 7}
|
||
|
# Note directory size is OS-specific, but must be >= 0
|
||
|
assert fs.du(tmpdir, withdirs=True) >= 11
|
||
|
|
||
|
d = fs.du(tmpdir, total=False, withdirs=True)
|
||
|
assert len(d) == 4
|
||
|
assert d[file_posix] == 4
|
||
|
assert d[subfile_posix] == 7
|
||
|
assert d[tmpdir_posix] >= 0
|
||
|
assert d[subdir_posix] >= 0
|
||
|
|
||
|
assert fs.du(tmpdir, maxdepth=2) == 11
|
||
|
assert fs.du(tmpdir, maxdepth=1) == 4
|
||
|
with pytest.raises(ValueError):
|
||
|
fs.du(tmpdir, maxdepth=0)
|
||
|
|
||
|
# Size of file only.
|
||
|
assert fs.du(file) == 4
|
||
|
assert fs.du(file, withdirs=True) == 4
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("funcname", ["cp", "get", "put"])
|
||
|
def test_cp_get_put_directory_recursive(tmpdir, funcname):
|
||
|
# https://github.com/fsspec/filesystem_spec/issues/1062
|
||
|
# Recursive cp/get/put of source directory into non-existent target directory.
|
||
|
fs = LocalFileSystem()
|
||
|
src = os.path.join(str(tmpdir), "src")
|
||
|
fs.mkdir(src)
|
||
|
fs.touch(os.path.join(src, "file"))
|
||
|
|
||
|
target = os.path.join(str(tmpdir), "target")
|
||
|
|
||
|
if funcname == "cp":
|
||
|
func = fs.cp
|
||
|
elif funcname == "get":
|
||
|
func = fs.get
|
||
|
elif funcname == "put":
|
||
|
func = fs.put
|
||
|
|
||
|
# cp/get/put without slash
|
||
|
assert not fs.exists(target)
|
||
|
for loop in range(2):
|
||
|
func(src, target, recursive=True)
|
||
|
assert fs.isdir(target)
|
||
|
|
||
|
if loop == 0:
|
||
|
assert fs.find(target) == [make_path_posix(os.path.join(target, "file"))]
|
||
|
else:
|
||
|
assert sorted(fs.find(target)) == [
|
||
|
make_path_posix(os.path.join(target, "file")),
|
||
|
make_path_posix(os.path.join(target, "src", "file")),
|
||
|
]
|
||
|
|
||
|
fs.rm(target, recursive=True)
|
||
|
|
||
|
# cp/get/put with slash
|
||
|
assert not fs.exists(target)
|
||
|
for loop in range(2):
|
||
|
func(src + "/", target, recursive=True)
|
||
|
assert fs.isdir(target)
|
||
|
assert fs.find(target) == [make_path_posix(os.path.join(target, "file"))]
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("funcname", ["cp", "get", "put"])
|
||
|
def test_cp_get_put_empty_directory(tmpdir, funcname):
|
||
|
# https://github.com/fsspec/filesystem_spec/issues/1198
|
||
|
# cp/get/put of empty directory.
|
||
|
fs = LocalFileSystem(auto_mkdir=True)
|
||
|
empty = os.path.join(str(tmpdir), "empty")
|
||
|
fs.mkdir(empty)
|
||
|
|
||
|
target = os.path.join(str(tmpdir), "target")
|
||
|
fs.mkdir(target)
|
||
|
|
||
|
if funcname == "cp":
|
||
|
func = fs.cp
|
||
|
elif funcname == "get":
|
||
|
func = fs.get
|
||
|
elif funcname == "put":
|
||
|
func = fs.put
|
||
|
|
||
|
# cp/get/put without slash, target directory exists
|
||
|
assert fs.isdir(target)
|
||
|
func(empty, target)
|
||
|
assert fs.find(target, withdirs=True) == [make_path_posix(target)]
|
||
|
|
||
|
# cp/get/put with slash, target directory exists
|
||
|
assert fs.isdir(target)
|
||
|
func(empty + "/", target)
|
||
|
assert fs.find(target, withdirs=True) == [make_path_posix(target)]
|
||
|
|
||
|
fs.rmdir(target)
|
||
|
|
||
|
# cp/get/put without slash, target directory doesn't exist
|
||
|
assert not fs.isdir(target)
|
||
|
func(empty, target)
|
||
|
assert not fs.isdir(target)
|
||
|
|
||
|
# cp/get/put with slash, target directory doesn't exist
|
||
|
assert not fs.isdir(target)
|
||
|
func(empty + "/", target)
|
||
|
assert not fs.isdir(target)
|
||
|
|
||
|
|
||
|
def test_cp_two_files(tmpdir):
|
||
|
fs = LocalFileSystem(auto_mkdir=True)
|
||
|
src = os.path.join(str(tmpdir), "src")
|
||
|
file0 = os.path.join(src, "file0")
|
||
|
file1 = os.path.join(src, "file1")
|
||
|
fs.mkdir(src)
|
||
|
fs.touch(file0)
|
||
|
fs.touch(file1)
|
||
|
|
||
|
target = os.path.join(str(tmpdir), "target")
|
||
|
assert not fs.exists(target)
|
||
|
|
||
|
fs.cp([file0, file1], target)
|
||
|
|
||
|
assert fs.isdir(target)
|
||
|
assert sorted(fs.find(target)) == [
|
||
|
make_path_posix(os.path.join(target, "file0")),
|
||
|
make_path_posix(os.path.join(target, "file1")),
|
||
|
]
|
||
|
|
||
|
|
||
|
@pytest.mark.skipif(WIN, reason="Windows does not support colons in filenames")
|
||
|
def test_issue_1447():
|
||
|
files_with_colons = {
|
||
|
".local:file:with:colons.txt": b"content1",
|
||
|
".colons-after-extension.txt:after": b"content2",
|
||
|
".colons-after-extension/file:colon.txt:before/after": b"content3",
|
||
|
}
|
||
|
with filetexts(files_with_colons, mode="b"):
|
||
|
for file, contents in files_with_colons.items():
|
||
|
with fsspec.filesystem("file").open(file, "rb") as f:
|
||
|
assert f.read() == contents
|
||
|
|
||
|
fs, urlpath = fsspec.core.url_to_fs(file)
|
||
|
assert isinstance(fs, fsspec.implementations.local.LocalFileSystem)
|
||
|
with fs.open(urlpath, "rb") as f:
|
||
|
assert f.read() == contents
|