From 428b4f91a4520c7adbc58b3c06d91820ddc7bcf1 Mon Sep 17 00:00:00 2001 From: AsherJingkongChen <37398747+AsherJingkongChen@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:44:55 +0800 Subject: [PATCH 1/3] Create temp file in target directory to prevent cross-device link error --- fsspec/implementations/local.py | 25 +++++++------------ fsspec/implementations/tests/test_local.py | 29 ++++++++++------------ 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/fsspec/implementations/local.py b/fsspec/implementations/local.py index 417447e5b..e9da2f083 100644 --- a/fsspec/implementations/local.py +++ b/fsspec/implementations/local.py @@ -399,7 +399,10 @@ def _open(self): self.f = compress(self.f, mode=self.mode) else: # TODO: check if path is writable? - i, name = tempfile.mkstemp() + i, name = tempfile.mkstemp( + dir=os.path.dirname(self.path) or None, + prefix=os.path.basename(self.path) + "-", + ) os.close(i) # we want normal open and normal buffered file self.temp = name self.f = open(name, mode=self.mode) @@ -438,22 +441,12 @@ def __getstate__(self): def commit(self): if self.autocommit: raise RuntimeError("Can only commit if not already set to autocommit") + shutil.move(self.temp, self.path) try: - shutil.move(self.temp, self.path) - except PermissionError as e: - # shutil.move raises PermissionError if os.rename - # and the default copy2 fallback with shutil.copystats fail. - # The file should be there nonetheless, but without copied permissions. - # If it doesn't exist, there was no permission to create the file. - if not os.path.exists(self.path): - raise e - else: - # If PermissionError is not raised, permissions can be set. - try: - mask = 0o666 - os.chmod(self.path, mask & ~get_umask(mask)) - except RuntimeError: - pass + mask = 0o666 + os.chmod(self.path, mask & ~get_umask(mask)) + except (RuntimeError, PermissionError): + pass def discard(self): if self.autocommit: diff --git a/fsspec/implementations/tests/test_local.py b/fsspec/implementations/tests/test_local.py index c3caf1ddb..53553ec16 100644 --- a/fsspec/implementations/tests/test_local.py +++ b/fsspec/implementations/tests/test_local.py @@ -1,5 +1,4 @@ import bz2 -import errno import gzip import os import os.path @@ -563,24 +562,22 @@ def test_multiple_filesystems_use_umask_cache(tmpdir): assert get_umask.cache_info().hits == 1 -def test_transaction_cross_device_but_mock_temp_dir_on_wrong_device(tmpdir): - # If the temporary file for a transaction is not on the correct device, - # os.rename in shutil.move will raise EXDEV and lookup('chmod') will raise - # a PermissionError. +def test_transaction_temp_file_in_target_dir(tmpdir): + # Temporary file should be created in the same directory as the target + # to avoid cross-device link errors when committing. fs = LocalFileSystem() - with ( - patch( - "os.rename", - side_effect=OSError(errno.EXDEV, "Invalid cross-device link"), - ), - patch( - "os.chmod", - side_effect=PermissionError("Operation not permitted"), - ), - ): - with fs.transaction, fs.open(tmpdir + "/afile", "wb") as f: + target = str(tmpdir) + "/subdir/file.txt" + os.makedirs(os.path.dirname(target)) + + with fs.transaction: + with fs.open(target, "wb") as f: + assert os.path.dirname(f.temp) == os.path.dirname(target) + assert os.path.basename(f.temp).startswith("file.txt-") f.write(b"data") + with fs.open(target, "rb") as f: + assert f.read() == b"data" + def test_make_path_posix(): cwd = os.getcwd() From e7efa02bc6b652e9f2d50c7898b60ad39055a526 Mon Sep 17 00:00:00 2001 From: AsherJingkongChen <37398747+AsherJingkongChen@users.noreply.github.com> Date: Wed, 21 Jan 2026 03:03:50 +0800 Subject: [PATCH 2/3] fix(pytest-win): use os.path.join for Windows path compatibility --- fsspec/implementations/tests/test_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsspec/implementations/tests/test_local.py b/fsspec/implementations/tests/test_local.py index 53553ec16..b8e8a8854 100644 --- a/fsspec/implementations/tests/test_local.py +++ b/fsspec/implementations/tests/test_local.py @@ -566,7 +566,7 @@ def test_transaction_temp_file_in_target_dir(tmpdir): # Temporary file should be created in the same directory as the target # to avoid cross-device link errors when committing. fs = LocalFileSystem() - target = str(tmpdir) + "/subdir/file.txt" + target = os.path.join(str(tmpdir), "subdir", "file.txt") os.makedirs(os.path.dirname(target)) with fs.transaction: From bbf200be832263a2e66d522ff8fdc7c4ed956f8e Mon Sep 17 00:00:00 2001 From: AsherJingkongChen <37398747+AsherJingkongChen@users.noreply.github.com> Date: Sat, 1 Aug 2026 04:02:57 +0800 Subject: [PATCH 3/3] Fall back to a static version when built outside a git checkout --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 79fe1428d..c522abc44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,7 +136,7 @@ Homepage = "https://github.com/fsspec/filesystem_spec" [tool.hatch.version] source = "vcs" -raw-options = { 'version_scheme' = 'post-release' } +raw-options = { 'version_scheme' = 'post-release', 'fallback_version' = '2026.1.0.post17' } [tool.hatch.build.hooks.vcs] version-file = "fsspec/_version.py"