diff --git a/.github/actions/setup-deps/action.yaml b/.github/actions/setup-deps/action.yaml index cbfd91df7e1..91dd56b6d7d 100644 --- a/.github/actions/setup-deps/action.yaml +++ b/.github/actions/setup-deps/action.yaml @@ -21,8 +21,8 @@ inputs: default: 'codecov' cython: default: 'cython' - fasteners: - default: 'fasteners' + filelock: + default: 'filelock' griddataformats: default: 'griddataformats' gsd: @@ -110,7 +110,7 @@ runs: CONDA_MIN_DEPS: | ${{ inputs.codecov }} ${{ inputs.cython }} - ${{ inputs.fasteners }} + ${{ inputs.filelock }} ${{ inputs.griddataformats }} ${{ inputs.hypothesis }} ${{ inputs.matplotlib }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 27b1c0db3f9..20a32d103ef 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,7 +90,7 @@ jobs: scikit-learn tqdm threadpoolctl - fasteners + filelock displayName: 'Install dependencies' # for wheel install testing, we pin to an # older NumPy, the oldest version we support and that diff --git a/maintainer/conda/environment.yml b/maintainer/conda/environment.yml index cd324d25cda..3ceeeadb2d2 100644 --- a/maintainer/conda/environment.yml +++ b/maintainer/conda/environment.yml @@ -7,7 +7,7 @@ dependencies: - codecov - cython - docutils - - fasteners + - filelock - griddataformats - gsd - h5py>=2.10 diff --git a/package/CHANGELOG b/package/CHANGELOG index 32a7374ff6b..7b5ce3b4c88 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -36,6 +36,7 @@ Enhancements Changes + * Changed `fasteners` dependency to `filelock` (Issue #4797, PR #4800) * Codebase is now formatted with black (version `24`) (PR #4886) Deprecations diff --git a/package/MDAnalysis/coordinates/XDR.py b/package/MDAnalysis/coordinates/XDR.py index 6fe75982cc4..bf036e34e51 100644 --- a/package/MDAnalysis/coordinates/XDR.py +++ b/package/MDAnalysis/coordinates/XDR.py @@ -38,7 +38,7 @@ import numpy as np from os.path import getctime, getsize, isfile, split, join import warnings -import fasteners +from filelock import FileLock from . import base from ..lib.mdamath import triclinic_box @@ -121,6 +121,8 @@ class XDRBaseReader(base.ReaderBase): Add a InterProcessLock when generating offsets .. versionchanged:: 2.4.0 Use a direct read into ts attributes + .. versionchanged:: 2.9.0 + Changed fasteners.InterProcessLock() to filelock.FileLock """ @store_init_arguments def __init__(self, filename, convert_units=True, sub=None, @@ -195,18 +197,18 @@ def _load_offsets(self): # check if the location of the lock is writable. try: - with fasteners.InterProcessLock(lock_name) as filelock: + with FileLock(lock_name) as filelock: pass except OSError as e: if isinstance(e, PermissionError) or e.errno == errno.EROFS: warnings.warn(f"Cannot write lock/offset file in same location as " f"{self.filename}. Using slow offset calculation.") - self._read_offsets(store=True) + self._read_offsets(store=False) return else: raise - with fasteners.InterProcessLock(lock_name) as filelock: + with FileLock(lock_name) as filelock: if not isfile(fname): self._read_offsets(store=True) return diff --git a/package/pyproject.toml b/package/pyproject.toml index 7e0ac361f26..238c09d0739 100644 --- a/package/pyproject.toml +++ b/package/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'tqdm>=4.43.0', 'threadpoolctl', 'packaging', - 'fasteners', + 'filelock', 'mda-xdrlib', 'waterdynamics', 'pathsimanalysis', diff --git a/package/requirements.txt b/package/requirements.txt index accac1b49fd..a196a9fe0f7 100644 --- a/package/requirements.txt +++ b/package/requirements.txt @@ -1,7 +1,7 @@ biopython>=1.80 codecov cython -fasteners +filelock griddataformats gsd hypothesis diff --git a/testsuite/MDAnalysisTests/coordinates/test_xdr.py b/testsuite/MDAnalysisTests/coordinates/test_xdr.py index 4a356eed042..2ca76580fcc 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_xdr.py +++ b/testsuite/MDAnalysisTests/coordinates/test_xdr.py @@ -25,8 +25,10 @@ import re import os +import sys import shutil import subprocess +import time from pathlib import Path import numpy as np @@ -60,6 +62,7 @@ from MDAnalysis.coordinates.base import Timestep from MDAnalysis.coordinates import XDR from MDAnalysisTests.util import get_userid +from filelock import FileLock @pytest.mark.parametrize( @@ -977,24 +980,25 @@ def test_unsupported_format(self, traj): reader = self._reader(traj) reader[idx_frame] - @pytest.mark.skipif(get_userid() == 0, reason="cannot readonly as root") - def test_persistent_offsets_readonly(self, tmpdir): + def test_persistent_offsets_readonly(self, tmpdir, trajectory): shutil.copy(self.filename, str(tmpdir)) - if os.name == "nt": - # Windows platform has a unique way to deny write access - subprocess.call( - "icacls {fname} /deny Users:W".format(fname=tmpdir), shell=True + filename = str(tmpdir.join(os.path.basename(self.filename))) + print('filename', filename) + ref_offset = trajectory._xdr.offsets + # Mock filelock acquire to raise an error + with patch.object(FileLock, "acquire", side_effect=PermissionError): # Simulate failure + with pytest.warns(UserWarning, match="Cannot write lock"): + reader = self._reader(filename) + saved_offsets = reader._xdr.offsets + + # Check if offsets are handled properly and match reference offsets + assert_almost_equal( + saved_offsets, # Compare with reference offsets + ref_offset, + err_msg="error loading frame offsets", ) - else: - os.chmod(str(tmpdir), 0o555) - filename = str(tmpdir.join(os.path.basename(self.filename))) - # try to write a offsets file - with pytest.warns( - UserWarning, match="Couldn't save offsets" - ) and pytest.warns(UserWarning, match="Cannot write"): - self._reader(filename) assert_equal(os.path.exists(XDR.offsets_filename(filename)), False) # check the lock file is not created as well. assert_equal( @@ -1002,20 +1006,13 @@ def test_persistent_offsets_readonly(self, tmpdir): False, ) - # pre-teardown permission fix - leaving permission blocked dir - # is problematic on py3.9 + Windows it seems. See issue - # [4123](https://github.com/MDAnalysis/mdanalysis/issues/4123) - # for more details. - if os.name == "nt": - subprocess.call(f"icacls {tmpdir} /grant Users:W", shell=True) - else: - os.chmod(str(tmpdir), 0o777) - - shutil.rmtree(tmpdir) - - def test_offset_lock_created(self): + @pytest.mark.skipif( + sys.platform.startswith("win"), + reason="The lock file only exists when it's locked in windows" + ) + def test_offset_lock_created(self, traj): assert os.path.exists( - XDR.offsets_filename(self.filename, ending="lock") + XDR.offsets_filename(traj, ending="lock") )