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
22 changes: 19 additions & 3 deletions fsspec/implementations/cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(
same_names: bool | None = None,
compression=None,
cache_mapper: AbstractCacheMapper | None = None,
cache_storage_mode=None,
**kwargs,
):
"""
Expand Down Expand Up @@ -118,6 +119,13 @@ def __init__(
cache_mapper: AbstractCacheMapper (optional)
The object use to map from original filenames to cached filenames.
Only one of this and ``same_names`` should be specified.
cache_storage_mode: int (optional)
Permission mode used when creating the cache storage directory,
e.g. ``0o700``. The default of ``None`` leaves the directory at
the system default (governed by the umask), which is the existing
behaviour. Pass an octal mode such as ``0o700`` to keep the cache
directory, and so the cached data files within it, readable by the
owner only.
"""
super().__init__(**kwargs)
if fs is None and target_protocol is None:
Expand All @@ -137,7 +145,8 @@ def __init__(
storage = [cache_storage]
else:
storage = cache_storage
os.makedirs(storage[-1], exist_ok=True)
self.cache_storage_mode = cache_storage_mode
self._makedirs(storage[-1])
self.storage = storage
self.kwargs = target_options or {}
self.cache_check = cache_check
Expand Down Expand Up @@ -184,8 +193,14 @@ def _remove_tempdir(tempdir):
except Exception:
pass

def _makedirs(self, path):
if self.cache_storage_mode is None:
os.makedirs(path, exist_ok=True)
else:
os.makedirs(path, exist_ok=True, mode=self.cache_storage_mode)

def _mkcache(self):
os.makedirs(self.storage[-1], exist_ok=True)
self._makedirs(self.storage[-1])

def cache_size(self):
"""Return size of cache in bytes.
Expand Down Expand Up @@ -452,6 +467,7 @@ def __getattribute__(self, item):
"isdir",
"_check_file",
"_check_cache",
"_makedirs",
"_mkcache",
"clear_cache",
"clear_expired_cache",
Expand Down Expand Up @@ -815,7 +831,7 @@ def __init__(self, **kwargs):
super().__init__(**kw)
for storage in self.storage:
if not os.path.exists(storage):
os.makedirs(storage, exist_ok=True)
self._makedirs(storage)

def _check_file(self, path):
self._check_cache()
Expand Down
21 changes: 21 additions & 0 deletions fsspec/implementations/tests/test_cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,27 @@ def test_constructor_kwargs(tmpdir):
)


@pytest.mark.skipif(win, reason="POSIX file permissions")
@pytest.mark.parametrize("protocol", ["filecache", "simplecache", "blockcache"])
def test_cache_storage_mode(tmp_path, protocol):
import stat

cache = tmp_path / "cache" # does not exist yet, fsspec must create it
old = os.umask(0o022)
try:
fsspec.filesystem(
protocol,
target_protocol="file",
cache_storage=str(cache),
cache_storage_mode=0o700,
)
finally:
os.umask(old)

mode = stat.S_IMODE(os.stat(cache).st_mode)
assert mode & (stat.S_IRWXG | stat.S_IRWXO) == 0, oct(mode)


def test_idempotent():
import pickle

Expand Down
Loading