Traktor/myenv/Lib/site-packages/fsspec/tests/test_spec.py
2024-05-23 01:57:24 +02:00

1168 lines
36 KiB
Python

import glob
import json
import os
import pickle
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
import numpy as np
import pytest
import fsspec
from fsspec.implementations.ftp import FTPFileSystem
from fsspec.implementations.http import HTTPFileSystem
from fsspec.implementations.local import LocalFileSystem
from fsspec.spec import AbstractBufferedFile, AbstractFileSystem
PATHS_FOR_GLOB_TESTS = (
{"name": "test0.json", "type": "file", "size": 100},
{"name": "test0.yaml", "type": "file", "size": 100},
{"name": "test0", "type": "directory", "size": 0},
{"name": "test0/test0.json", "type": "file", "size": 100},
{"name": "test0/test0.yaml", "type": "file", "size": 100},
{"name": "test0/test1", "type": "directory", "size": 0},
{"name": "test0/test1/test0.json", "type": "file", "size": 100},
{"name": "test0/test1/test0.yaml", "type": "file", "size": 100},
{"name": "test0/test1/test2", "type": "directory", "size": 0},
{"name": "test0/test1/test2/test0.json", "type": "file", "size": 100},
{"name": "test0/test1/test2/test0.yaml", "type": "file", "size": 100},
{"name": "test0/test2", "type": "directory", "size": 0},
{"name": "test0/test2/test0.json", "type": "file", "size": 100},
{"name": "test0/test2/test0.yaml", "type": "file", "size": 100},
{"name": "test0/test2/test1", "type": "directory", "size": 0},
{"name": "test0/test2/test1/test0.json", "type": "file", "size": 100},
{"name": "test0/test2/test1/test0.yaml", "type": "file", "size": 100},
{"name": "test0/test2/test1/test3", "type": "directory", "size": 0},
{"name": "test0/test2/test1/test3/test0.json", "type": "file", "size": 100},
{"name": "test0/test2/test1/test3/test0.yaml", "type": "file", "size": 100},
{"name": "test1.json", "type": "file", "size": 100},
{"name": "test1.yaml", "type": "file", "size": 100},
{"name": "test1", "type": "directory", "size": 0},
{"name": "test1/test0.json", "type": "file", "size": 100},
{"name": "test1/test0.yaml", "type": "file", "size": 100},
{"name": "test1/test0", "type": "directory", "size": 0},
{"name": "test1/test0/test0.json", "type": "file", "size": 100},
{"name": "test1/test0/test0.yaml", "type": "file", "size": 100},
{"name": "special_chars", "type": "directory", "size": 0},
{"name": "special_chars/f\\oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f.oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f+oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f(oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f)oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f|oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f^oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f$oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f{oo.txt", "type": "file", "size": 100},
{"name": "special_chars/f}oo.txt", "type": "file", "size": 100},
)
GLOB_POSIX_TESTS = {
"argnames": ("path", "expected"),
"argvalues": [
("nonexistent", []),
("test0.json", ["test0.json"]),
("test0", ["test0"]),
("test0/", ["test0"]),
("test1/test0.yaml", ["test1/test0.yaml"]),
("test0/test[1-2]", ["test0/test1", "test0/test2"]),
("test0/test[1-2]/", ["test0/test1", "test0/test2"]),
(
"test0/test[1-2]/*",
[
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test2/test0.json",
"test0/test2/test0.yaml",
"test0/test2/test1",
],
),
(
"test0/test[1-2]/*.[j]*",
["test0/test1/test0.json", "test0/test2/test0.json"],
),
("special_chars/f\\oo.*", ["special_chars/f\\oo.txt"]),
("special_chars/f.oo.*", ["special_chars/f.oo.txt"]),
("special_chars/f+oo.*", ["special_chars/f+oo.txt"]),
("special_chars/f(oo.*", ["special_chars/f(oo.txt"]),
("special_chars/f)oo.*", ["special_chars/f)oo.txt"]),
("special_chars/f|oo.*", ["special_chars/f|oo.txt"]),
("special_chars/f^oo.*", ["special_chars/f^oo.txt"]),
("special_chars/f$oo.*", ["special_chars/f$oo.txt"]),
("special_chars/f{oo.*", ["special_chars/f{oo.txt"]),
("special_chars/f}oo.*", ["special_chars/f}oo.txt"]),
(
"*",
[
"special_chars",
"test0.json",
"test0.yaml",
"test0",
"test1.json",
"test1.yaml",
"test1",
],
),
("*.yaml", ["test0.yaml", "test1.yaml"]),
(
"**",
[
"special_chars",
"special_chars/f$oo.txt",
"special_chars/f(oo.txt",
"special_chars/f)oo.txt",
"special_chars/f+oo.txt",
"special_chars/f.oo.txt",
"special_chars/f\\oo.txt",
"special_chars/f^oo.txt",
"special_chars/f{oo.txt",
"special_chars/f|oo.txt",
"special_chars/f}oo.txt",
"test0.json",
"test0.yaml",
"test0",
"test0/test0.json",
"test0/test0.yaml",
"test0/test1",
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test1/test2/test0.json",
"test0/test1/test2/test0.yaml",
"test0/test2",
"test0/test2/test0.json",
"test0/test2/test0.yaml",
"test0/test2/test1",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test0/test2/test1/test3/test0.json",
"test0/test2/test1/test3/test0.yaml",
"test1.json",
"test1.yaml",
"test1",
"test1/test0.json",
"test1/test0.yaml",
"test1/test0",
"test1/test0/test0.json",
"test1/test0/test0.yaml",
],
),
("*/", ["special_chars", "test0", "test1"]),
(
"**/",
[
"special_chars",
"test0",
"test0/test1",
"test0/test1/test2",
"test0/test2",
"test0/test2/test1",
"test0/test2/test1/test3",
"test1",
"test1/test0",
],
),
("*/*.yaml", ["test0/test0.yaml", "test1/test0.yaml"]),
(
"**/*.yaml",
[
"test0.yaml",
"test0/test0.yaml",
"test0/test1/test0.yaml",
"test0/test1/test2/test0.yaml",
"test0/test2/test0.yaml",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3/test0.yaml",
"test1.yaml",
"test1/test0.yaml",
"test1/test0/test0.yaml",
],
),
(
"*/test1/*",
["test0/test1/test0.json", "test0/test1/test0.yaml", "test0/test1/test2"],
),
("*/test1/*.yaml", ["test0/test1/test0.yaml"]),
(
"**/test1/*",
[
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test1/test0.json",
"test1/test0.yaml",
"test1/test0",
],
),
(
"**/test1/*.yaml",
[
"test0/test1/test0.yaml",
"test0/test2/test1/test0.yaml",
"test1/test0.yaml",
],
),
("*/test1/*/", ["test0/test1/test2"]),
(
"**/test1/*/",
["test0/test1/test2", "test0/test2/test1/test3", "test1/test0"],
),
(
"*/test1/**",
[
"test0/test1",
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test1/test2/test0.json",
"test0/test1/test2/test0.yaml",
],
),
(
"**/test1/**",
[
"test0/test1",
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test1/test2/test0.json",
"test0/test1/test2/test0.yaml",
"test0/test2/test1",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test0/test2/test1/test3/test0.json",
"test0/test2/test1/test3/test0.yaml",
"test1",
"test1/test0.json",
"test1/test0.yaml",
"test1/test0",
"test1/test0/test0.json",
"test1/test0/test0.yaml",
],
),
("*/test1/**/", ["test0/test1", "test0/test1/test2"]),
(
"**/test1/**/",
[
"test0/test1",
"test0/test1/test2",
"test0/test2/test1",
"test0/test2/test1/test3",
"test1",
"test1/test0",
],
),
(
"test0/*",
["test0/test0.json", "test0/test0.yaml", "test0/test1", "test0/test2"],
),
("test0/*.yaml", ["test0/test0.yaml"]),
(
"test0/**",
[
"test0",
"test0/test0.json",
"test0/test0.yaml",
"test0/test1",
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test1/test2/test0.json",
"test0/test1/test2/test0.yaml",
"test0/test2",
"test0/test2/test0.json",
"test0/test2/test0.yaml",
"test0/test2/test1",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test0/test2/test1/test3/test0.json",
"test0/test2/test1/test3/test0.yaml",
],
),
("test0/*/", ["test0/test1", "test0/test2"]),
(
"test0/**/",
[
"test0",
"test0/test1",
"test0/test1/test2",
"test0/test2",
"test0/test2/test1",
"test0/test2/test1/test3",
],
),
("test0/*/*.yaml", ["test0/test1/test0.yaml", "test0/test2/test0.yaml"]),
(
"test0/**/*.yaml",
[
"test0/test0.yaml",
"test0/test1/test0.yaml",
"test0/test1/test2/test0.yaml",
"test0/test2/test0.yaml",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3/test0.yaml",
],
),
(
"test0/*/test1/*",
[
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
],
),
("test0/*/test1/*.yaml", ["test0/test2/test1/test0.yaml"]),
(
"test0/**/test1/*",
[
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
],
),
(
"test0/**/test1/*.yaml",
["test0/test1/test0.yaml", "test0/test2/test1/test0.yaml"],
),
("test0/*/test1/*/", ["test0/test2/test1/test3"]),
("test0/**/test1/*/", ["test0/test1/test2", "test0/test2/test1/test3"]),
(
"test0/*/test1/**",
[
"test0/test2/test1",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test0/test2/test1/test3/test0.json",
"test0/test2/test1/test3/test0.yaml",
],
),
(
"test0/**/test1/**",
[
"test0/test1",
"test0/test1/test0.json",
"test0/test1/test0.yaml",
"test0/test1/test2",
"test0/test1/test2/test0.json",
"test0/test1/test2/test0.yaml",
"test0/test2/test1",
"test0/test2/test1/test0.json",
"test0/test2/test1/test0.yaml",
"test0/test2/test1/test3",
"test0/test2/test1/test3/test0.json",
"test0/test2/test1/test3/test0.yaml",
],
),
("test0/*/test1/**/", ["test0/test2/test1", "test0/test2/test1/test3"]),
(
"test0/**/test1/**/",
[
"test0/test1",
"test0/test1/test2",
"test0/test2/test1",
"test0/test2/test1/test3",
],
),
],
}
class DummyTestFS(AbstractFileSystem):
protocol = "mock"
_file_class = AbstractBufferedFile
_fs_contents = (
{"name": "top_level", "type": "directory"},
{"name": "top_level/second_level", "type": "directory"},
{"name": "top_level/second_level/date=2019-10-01", "type": "directory"},
{
"name": "top_level/second_level/date=2019-10-01/a.parquet",
"type": "file",
"size": 100,
},
{
"name": "top_level/second_level/date=2019-10-01/b.parquet",
"type": "file",
"size": 100,
},
{"name": "top_level/second_level/date=2019-10-02", "type": "directory"},
{
"name": "top_level/second_level/date=2019-10-02/a.parquet",
"type": "file",
"size": 100,
},
{"name": "top_level/second_level/date=2019-10-04", "type": "directory"},
{
"name": "top_level/second_level/date=2019-10-04/a.parquet",
"type": "file",
"size": 100,
},
{"name": "misc", "type": "directory"},
{"name": "misc/foo.txt", "type": "file", "size": 100},
)
def __init__(self, fs_content=None, **kwargs):
if fs_content is not None:
self._fs_contents = fs_content
super().__init__(**kwargs)
def __getitem__(self, name):
for item in self._fs_contents:
if item["name"] == name:
return item
raise IndexError(f"{name} not found!")
def ls(self, path, detail=True, refresh=True, **kwargs):
if kwargs.pop("strip_proto", True):
path = self._strip_protocol(path)
files = not refresh and self._ls_from_cache(path)
if not files:
files = [
file for file in self._fs_contents if path == self._parent(file["name"])
]
files.sort(key=lambda file: file["name"])
self.dircache[path.rstrip("/")] = files
if detail:
return files
return [file["name"] for file in files]
@classmethod
def get_test_paths(cls, start_with=""):
"""Helper to return directory and file paths with no details"""
all = [
file["name"]
for file in cls._fs_contents
if file["name"].startswith(start_with)
]
return all
def _open(
self,
path,
mode="rb",
block_size=None,
autocommit=True,
cache_options=None,
**kwargs,
):
return self._file_class(
self,
path,
mode,
block_size,
autocommit,
cache_options=cache_options,
**kwargs,
)
@pytest.mark.parametrize(
["test_paths", "recursive", "maxdepth", "expected"],
[
(
(
"top_level/second_level",
"top_level/sec*",
"top_level/sec*vel",
"top_level/*",
),
True,
None,
[
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-01/b.parquet",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
(
"top_level/second_level",
"top_level/sec*",
"top_level/sec*vel",
"top_level/*",
),
False,
None,
[
"top_level/second_level",
],
),
(
("top_level/second_level",),
True,
1,
[
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-04",
],
),
(
("top_level/second_level",),
True,
2,
[
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-01/b.parquet",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
("top_level/*", "top_level/sec*", "top_level/sec*vel", "top_level/*"),
True,
1,
["top_level/second_level"],
),
(
("top_level/*", "top_level/sec*", "top_level/sec*vel", "top_level/*"),
True,
2,
[
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-04",
],
),
(
("top_level/**",),
False,
None,
[
"top_level",
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-01/b.parquet",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
("top_level/**",),
True,
None,
[
"top_level",
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-01/b.parquet",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(("top_level/**",), True, 1, ["top_level", "top_level/second_level"]),
(
("top_level/**",),
True,
2,
[
"top_level",
"top_level/second_level",
"top_level/second_level/date=2019-10-01",
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-01/b.parquet",
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
("top_level/**/a.*",),
False,
None,
[
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
("top_level/**/a.*",),
True,
None,
[
"top_level/second_level/date=2019-10-01/a.parquet",
"top_level/second_level/date=2019-10-02/a.parquet",
"top_level/second_level/date=2019-10-04/a.parquet",
],
),
(
("top_level/**/second_level/date=2019-10-02",),
False,
2,
[
"top_level/second_level/date=2019-10-02",
],
),
(
("top_level/**/second_level/date=2019-10-02",),
True,
2,
[
"top_level/second_level/date=2019-10-02",
"top_level/second_level/date=2019-10-02/a.parquet",
],
),
[("misc/foo.txt", "misc/*.txt"), False, None, ["misc/foo.txt"]],
[("misc/foo.txt", "misc/*.txt"), True, None, ["misc/foo.txt"]],
(
("",),
False,
None,
[DummyTestFS.root_marker],
),
(
("",),
True,
None,
DummyTestFS.get_test_paths() + [DummyTestFS.root_marker],
),
[
(Path("misc/foo.txt"),),
False,
None,
[f"misc{os.sep}foo.txt"],
],
],
)
def test_expand_path(test_paths, recursive, maxdepth, expected):
"""Test a number of paths and then their combination which should all yield
the same set of expanded paths"""
test_fs = DummyTestFS()
# test single query
for test_path in test_paths:
paths = test_fs.expand_path(test_path, recursive=recursive, maxdepth=maxdepth)
assert sorted(paths) == sorted(expected)
# test with all queries
paths = test_fs.expand_path(
list(test_paths), recursive=recursive, maxdepth=maxdepth
)
assert sorted(paths) == sorted(expected)
def test_expand_paths_with_wrong_args():
test_fs = DummyTestFS()
with pytest.raises(ValueError):
test_fs.expand_path("top_level", recursive=True, maxdepth=0)
with pytest.raises(ValueError):
test_fs.expand_path("top_level", maxdepth=0)
with pytest.raises(FileNotFoundError):
test_fs.expand_path("top_level/**/second_level/date=2019-10-02", maxdepth=1)
with pytest.raises(FileNotFoundError):
test_fs.expand_path("nonexistent/*")
@pytest.mark.xfail
def test_find():
"""Test .find() method on debian server (ftp, https) with constant folder"""
filesystem, host, test_path = (
FTPFileSystem,
"ftp.fau.de",
"ftp://ftp.fau.de/debian-cd/current/amd64/log/success",
)
test_fs = filesystem(host)
filenames_ftp = test_fs.find(test_path)
assert filenames_ftp
filesystem, host, test_path = (
HTTPFileSystem,
"https://ftp.fau.de",
"https://ftp.fau.de/debian-cd/current/amd64/log/success",
)
test_fs = filesystem()
filenames_http = test_fs.find(test_path)
roots = [f.rsplit("/", 1)[-1] for f in filenames_http]
assert all(f.rsplit("/", 1)[-1] in roots for f in filenames_ftp)
def test_find_details():
test_fs = DummyTestFS()
filenames = test_fs.find("/")
details = test_fs.find("/", detail=True)
for filename in filenames:
assert details[filename] == test_fs.info(filename)
def test_find_file():
test_fs = DummyTestFS()
filename = "misc/foo.txt"
assert test_fs.find(filename) == [filename]
assert test_fs.find(filename, detail=True) == {filename: {}}
def test_cache():
fs = DummyTestFS()
fs2 = DummyTestFS()
assert fs is fs2
assert DummyTestFS.current() is fs
assert len(fs._cache) == 1
del fs2
assert len(fs._cache) == 1
del fs
# keeps and internal reference, doesn't get collected
assert len(DummyTestFS._cache) == 1
DummyTestFS.clear_instance_cache()
assert len(DummyTestFS._cache) == 0
def test_current():
fs = DummyTestFS()
fs2 = DummyTestFS(arg=1)
assert fs is not fs2
assert DummyTestFS.current() is fs2
DummyTestFS()
assert DummyTestFS.current() is fs
def test_alias():
with pytest.warns(FutureWarning, match="add_aliases"):
DummyTestFS(add_aliases=True)
def test_add_docs_warns():
with pytest.warns(FutureWarning, match="add_docs"):
AbstractFileSystem(add_docs=True)
def test_cache_options():
fs = DummyTestFS()
f = AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes")
assert f.cache.trim
# TODO: dummy buffered file
f = AbstractBufferedFile(
fs, "misc/foo.txt", cache_type="bytes", cache_options={"trim": False}
)
assert f.cache.trim is False
f = fs.open("misc/foo.txt", cache_type="bytes", cache_options={"trim": False})
assert f.cache.trim is False
def test_trim_kwarg_warns():
fs = DummyTestFS()
with pytest.warns(FutureWarning, match="cache_options"):
AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes", trim=False)
def tests_file_open_error(monkeypatch):
class InitiateError(ValueError): ...
class UploadError(ValueError): ...
class DummyBufferedFile(AbstractBufferedFile):
can_initiate = False
def _initiate_upload(self):
if not self.can_initiate:
raise InitiateError
def _upload_chunk(self, final=False):
raise UploadError
monkeypatch.setattr(DummyTestFS, "_file_class", DummyBufferedFile)
fs = DummyTestFS()
with pytest.raises(InitiateError):
with fs.open("misc/foo.txt", "wb") as stream:
stream.write(b"hello" * stream.blocksize * 2)
with pytest.raises(UploadError):
with fs.open("misc/foo.txt", "wb") as stream:
stream.can_initiate = True
stream.write(b"hello" * stream.blocksize * 2)
def test_eq():
fs = DummyTestFS()
result = fs == 1
assert result is False
f = AbstractBufferedFile(fs, "misc/foo.txt", cache_type="bytes")
result = f == 1
assert result is False
def test_pickle_multiple():
a = DummyTestFS(1)
b = DummyTestFS(2, bar=1)
x = pickle.dumps(a)
y = pickle.dumps(b)
del a, b
DummyTestFS.clear_instance_cache()
result = pickle.loads(x)
assert result.storage_args == (1,)
assert result.storage_options == {}
result = pickle.loads(y)
assert result.storage_args == (2,)
assert result.storage_options == {"bar": 1}
def test_json():
a = DummyTestFS(1)
b = DummyTestFS(2, bar=1)
outa = a.to_json()
outb = b.to_json()
assert json.loads(outb) # is valid JSON
assert a != b
assert "bar" in outb
assert DummyTestFS.from_json(outa) is a
assert DummyTestFS.from_json(outb) is b
def test_ls_from_cache():
fs = DummyTestFS()
uncached_results = fs.ls("top_level/second_level/", refresh=True)
assert fs.ls("top_level/second_level/", refresh=False) == uncached_results
# _strip_protocol removes everything by default though
# for the sake of testing the _ls_from_cache interface
# directly, we need run one time more without that call
# to actually verify that our stripping in the client
# function works.
assert (
fs.ls("top_level/second_level/", refresh=False, strip_proto=False)
== uncached_results
)
@pytest.mark.parametrize(
"dt",
[
np.int8,
np.int16,
np.int32,
np.int64,
np.uint8,
np.uint16,
np.uint32,
np.uint64,
np.float32,
np.float64,
],
)
def test_readinto_with_numpy(tmpdir, dt):
store_path = str(tmpdir / "test_arr.npy")
arr = np.arange(10, dtype=dt)
arr.tofile(store_path)
arr2 = np.empty_like(arr)
with fsspec.open(store_path, "rb") as f:
f.readinto(arr2)
assert np.array_equal(arr, arr2)
@pytest.mark.parametrize(
"dt",
[
np.int8,
np.int16,
np.int32,
np.int64,
np.uint8,
np.uint16,
np.uint32,
np.uint64,
np.float32,
np.float64,
],
)
def test_readinto_with_multibyte(ftp_writable, tmpdir, dt):
host, port, user, pw = ftp_writable
ftp = FTPFileSystem(host=host, port=port, username=user, password=pw)
with ftp.open("/out", "wb") as fp:
arr = np.arange(10, dtype=dt)
fp.write(arr.tobytes())
with ftp.open("/out", "rb") as fp:
arr2 = np.empty_like(arr)
fp.readinto(arr2)
assert np.array_equal(arr, arr2)
class DummyOpenFS(DummyTestFS):
blocksize = 10
def _open(self, path, mode="rb", **kwargs):
stream = open(path, mode)
stream.size = os.stat(path).st_size
return stream
class BasicCallback(fsspec.Callback):
def __init__(self, **kwargs):
self.events = []
super().__init__(**kwargs)
def set_size(self, size):
self.events.append(("set_size", size))
def relative_update(self, inc=1):
self.events.append(("relative_update", inc))
def imitate_transfer(size, chunk, *, file=True):
events = [("set_size", size)]
events.extend(("relative_update", size // chunk) for _ in range(chunk))
if file:
# The reason that there is a relative_update(0) at the
# end is that, we don't have an early exit on the
# implementations of get_file/put_file so it needs to
# go through the callback to get catch by the while's
# condition and then it will stop the transfer.
events.append(("relative_update", 0))
return events
def get_files(tmpdir, amount=10):
src, dest, base = [], [], []
for index in range(amount):
src_path = tmpdir / f"src_{index}.txt"
src_path.write_text("x" * 50, "utf-8")
src.append(str(src_path))
dest.append(str(tmpdir / f"dst_{index}.txt"))
base.append(str(tmpdir / f"file_{index}.txt"))
return src, dest, base
def test_dummy_callbacks_file(tmpdir):
fs = DummyOpenFS()
callback = BasicCallback()
file = tmpdir / "file.txt"
source = tmpdir / "tmp.txt"
destination = tmpdir / "tmp2.txt"
size = 100
source.write_text("x" * 100, "utf-8")
fs.put_file(source, file, callback=callback)
# -1 here since put_file no longer has final zero-size put
assert callback.events == imitate_transfer(size, 10)[:-1]
callback.events.clear()
fs.get_file(file, destination, callback=callback)
assert callback.events == imitate_transfer(size, 10)
callback.events.clear()
assert destination.read_text("utf-8") == "x" * 100
def test_dummy_callbacks_files(tmpdir):
fs = DummyOpenFS()
callback = BasicCallback()
src, dest, base = get_files(tmpdir)
fs.put(src, base, callback=callback)
assert callback.events == imitate_transfer(10, 10, file=False)
callback.events.clear()
fs.get(base, dest, callback=callback)
assert callback.events == imitate_transfer(10, 10, file=False)
class BranchableCallback(BasicCallback):
def __init__(self, source, dest=None, events=None, **kwargs):
super().__init__(**kwargs)
if dest:
self.key = source, dest
else:
self.key = (source,)
self.events = events or defaultdict(list)
def branch(self, path_1, path_2, kwargs):
from fsspec.implementations.local import make_path_posix
path_1 = make_path_posix(path_1)
path_2 = make_path_posix(path_2)
kwargs["callback"] = BranchableCallback(path_1, path_2, events=self.events)
def set_size(self, size):
self.events[self.key].append(("set_size", size))
def relative_update(self, inc=1):
self.events[self.key].append(("relative_update", inc))
def test_dummy_callbacks_files_branched(tmpdir):
fs = DummyOpenFS()
src, dest, base = get_files(tmpdir)
callback = BranchableCallback("top-level")
def check_events(lpaths, rpaths):
from fsspec.implementations.local import make_path_posix
base_keys = zip(make_path_posix(lpaths), make_path_posix(rpaths))
assert set(callback.events.keys()) == {("top-level",), *base_keys}
assert callback.events["top-level",] == imitate_transfer(10, 10, file=False)
for key in base_keys:
assert callback.events[key] == imitate_transfer(50, 5)
fs.put(src, base, callback=callback)
check_events(src, base)
callback.events.clear()
fs.get(base, dest, callback=callback)
check_events(base, dest)
callback.events.clear()
def _clean_paths(paths, prefix=""):
"""
Helper to cleanup paths results by doing the following:
- remove the prefix provided from all paths
- remove the trailing slashes from all paths
- remove duplicates paths
- sort all paths
"""
paths_list = paths
if isinstance(paths, dict):
paths_list = list(paths)
paths_list = [p.replace(prefix, "").strip("/") for p in sorted(set(paths_list))]
if isinstance(paths, dict):
return {p: paths[p] for p in paths_list}
return paths_list
@pytest.fixture(scope="function")
def glob_fs():
return DummyTestFS(fs_content=PATHS_FOR_GLOB_TESTS)
@pytest.fixture(scope="function")
def glob_files_folder(tmp_path):
local_fs = LocalFileSystem(auto_mkdir=True)
local_fake_dir = str(tmp_path)
for path_info in PATHS_FOR_GLOB_TESTS:
if path_info["type"] == "file":
local_fs.touch(path=f"{str(tmp_path)}/{path_info['name']}")
return local_fake_dir
@pytest.mark.skipif(
sys.platform.startswith("win"),
reason="no need to run python glob posix tests on windows",
)
@pytest.mark.parametrize(
GLOB_POSIX_TESTS["argnames"],
GLOB_POSIX_TESTS["argvalues"],
)
def test_posix_tests_python_glob(path, expected, glob_files_folder):
"""
Tests against python glob to check if our posix tests are accurate.
"""
os.chdir(glob_files_folder)
python_output = glob.glob(pathname=path, recursive=True)
assert _clean_paths(python_output, glob_files_folder) == _clean_paths(expected)
@pytest.mark.skipif(
sys.platform.startswith("win"),
reason="no need to run bash stat posix tests on windows",
)
@pytest.mark.parametrize(
GLOB_POSIX_TESTS["argnames"],
GLOB_POSIX_TESTS["argvalues"],
)
def test_posix_tests_bash_stat(path, expected, glob_files_folder):
"""
Tests against bash stat to check if our posix tests are accurate.
"""
try:
subprocess.check_output(["bash", "-c", "shopt -s globstar"])
except FileNotFoundError:
pytest.skip("bash is not available")
except subprocess.CalledProcessError:
pytest.skip("globstar option is not available")
bash_path = (
path.replace("\\", "\\\\")
.replace("$", "\\$")
.replace("(", "\\(")
.replace(")", "\\)")
.replace("|", "\\|")
)
bash_output = subprocess.run(
[
"bash",
"-c",
f"cd {glob_files_folder} && shopt -s globstar && stat -c %N {bash_path}",
],
capture_output=True,
check=False,
)
# Remove the last element always empty
bash_output = bash_output.stdout.decode("utf-8").replace("'", "").split("\n")[:-1]
assert _clean_paths(bash_output, glob_files_folder) == _clean_paths(expected)
@pytest.mark.parametrize(
GLOB_POSIX_TESTS["argnames"],
GLOB_POSIX_TESTS["argvalues"],
)
def test_glob_posix_rules(path, expected, glob_fs):
output = glob_fs.glob(path=f"mock://{path}")
assert _clean_paths(output) == _clean_paths(expected)
detailed_output = glob_fs.glob(path=f"mock://{path}", detail=True)
for name, info in _clean_paths(detailed_output).items():
assert info == glob_fs[name]