Skip to content

Commit 8600919

Browse files
authored
fix: keep same permissions when using transaction (#1826)
1 parent a82b087 commit 8600919

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

fsspec/implementations/local.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import shutil
77
import stat
88
import tempfile
9+
from functools import lru_cache
910

1011
from fsspec import AbstractFileSystem
1112
from fsspec.compression import compr
@@ -354,6 +355,19 @@ def trailing_sep(path):
354355
return path.endswith(os.sep) or (os.altsep is not None and path.endswith(os.altsep))
355356

356357

358+
@lru_cache(maxsize=1)
359+
def get_umask(mask: int = 0o666) -> int:
360+
"""Get the current umask.
361+
362+
Follows https://stackoverflow.com/a/44130549 to get the umask.
363+
Temporarily sets the umask to the given value, and then resets it to the
364+
original value.
365+
"""
366+
value = os.umask(mask)
367+
os.umask(value)
368+
return value
369+
370+
357371
class LocalFileOpener(io.IOBase):
358372
def __init__(
359373
self, path, mode, autocommit=True, fs=None, compression=None, **kwargs
@@ -417,6 +431,11 @@ def commit(self):
417431
if self.autocommit:
418432
raise RuntimeError("Can only commit if not already set to autocommit")
419433
shutil.move(self.temp, self.path)
434+
try:
435+
mask = 0o666
436+
os.chmod(self.path, mask & ~get_umask(mask))
437+
except RuntimeError:
438+
pass
420439

421440
def discard(self):
422441
if self.autocommit:

fsspec/implementations/tests/test_local.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import fsspec
1616
from fsspec import compression
1717
from fsspec.core import OpenFile, get_fs_token_paths, open_files
18-
from fsspec.implementations.local import LocalFileSystem, make_path_posix
18+
from fsspec.implementations.local import LocalFileSystem, get_umask, make_path_posix
1919
from fsspec.tests.test_utils import WIN
2020

2121
files = {
@@ -508,6 +508,60 @@ def test_commit_discard(tmpdir):
508508
assert not fs.exists(tmpdir + "/bfile")
509509

510510

511+
def test_same_permissions_with_and_without_transaction(tmpdir):
512+
tmpdir = str(tmpdir)
513+
514+
with fsspec.open(tmpdir + "/afile", "wb") as f:
515+
f.write(b"data")
516+
517+
fs, urlpath = fsspec.core.url_to_fs(tmpdir + "/bfile")
518+
with fs.transaction, fs.open(urlpath, "wb") as f:
519+
f.write(b"data")
520+
521+
assert fs.info(tmpdir + "/afile")["mode"] == fs.info(tmpdir + "/bfile")["mode"]
522+
523+
524+
def test_get_umask_uses_cache():
525+
get_umask.cache_clear() # Clear the cache before the test for testing purposes.
526+
get_umask() # Retrieve the current umask value.
527+
get_umask() # Retrieve the umask again; this should result in a cache hit.
528+
assert get_umask.cache_info().hits == 1
529+
530+
531+
def test_multiple_file_transaction_use_umask_cache(tmpdir):
532+
get_umask.cache_clear() # Clear the cache before the test for testing purposes.
533+
fs = LocalFileSystem()
534+
with fs.transaction:
535+
with fs.open(tmpdir + "/afile", "wb") as f:
536+
f.write(b"data")
537+
with fs.open(tmpdir + "/bfile", "wb") as f:
538+
f.write(b"data")
539+
assert get_umask.cache_info().hits == 1
540+
541+
542+
def test_multiple_transactions_use_umask_cache(tmpdir):
543+
get_umask.cache_clear() # Clear the cache before the test for testing purposes.
544+
fs = LocalFileSystem()
545+
with fs.transaction:
546+
with fs.open(tmpdir + "/afile", "wb") as f:
547+
f.write(b"data")
548+
with fs.transaction:
549+
with fs.open(tmpdir + "/bfile", "wb") as f:
550+
f.write(b"data")
551+
assert get_umask.cache_info().hits == 1
552+
553+
554+
def test_multiple_filesystems_use_umask_cache(tmpdir):
555+
get_umask.cache_clear() # Clear the cache before the test for testing purposes.
556+
fs1 = LocalFileSystem()
557+
fs2 = LocalFileSystem()
558+
with fs1.transaction, fs1.open(tmpdir + "/afile", "wb") as f:
559+
f.write(b"data")
560+
with fs2.transaction, fs2.open(tmpdir + "/bfile", "wb") as f:
561+
f.write(b"data")
562+
assert get_umask.cache_info().hits == 1
563+
564+
511565
def test_make_path_posix():
512566
cwd = os.getcwd()
513567
if WIN:

0 commit comments

Comments
 (0)