From e47e8acd37a8e41c96b4c7702dd86a9184af83f6 Mon Sep 17 00:00:00 2001 From: myheroyuki Date: Tue, 3 May 2022 22:12:51 +0900 Subject: [PATCH 1/6] Add ability to return immediately when a lock cannot be obtained instantly --- src/filelock/__init__.py | 3 ++- src/filelock/_api.py | 8 +++++++- src/filelock/_error.py | 11 +++++++++++ tests/test_filelock.py | 26 +++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/filelock/__init__.py b/src/filelock/__init__.py index afcdb706..6f1e0d0a 100644 --- a/src/filelock/__init__.py +++ b/src/filelock/__init__.py @@ -11,7 +11,7 @@ import warnings from ._api import AcquireReturnProxy, BaseFileLock -from ._error import Timeout +from ._error import Timeout, ImmediateAquireError from ._soft import SoftFileLock from ._unix import UnixFileLock, has_fcntl from ._windows import WindowsFileLock @@ -45,4 +45,5 @@ "WindowsFileLock", "BaseFileLock", "AcquireReturnProxy", + "ImmediateAquireError", ] diff --git a/src/filelock/_api.py b/src/filelock/_api.py index 282106fd..e8e619b7 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -10,7 +10,7 @@ from types import TracebackType from typing import Any -from ._error import Timeout +from ._error import Timeout, ImmediateAquireError _LOGGER = logging.getLogger("filelock") @@ -116,6 +116,7 @@ def acquire( poll_interval: float = 0.05, *, poll_intervall: float | None = None, + return_immediately = False ) -> AcquireReturnProxy: """ Try to acquire the file lock. @@ -124,6 +125,8 @@ def acquire( if ``timeout < 0``, there is no timeout and this method will block until the lock could be acquired :param poll_interval: interval of trying to acquire the lock file :param poll_intervall: deprecated, kept for backwards compatibility, use ``poll_interval`` instead + :param return_immediately: defaults to False. If True, function will return immediately if it cannot obtain + a lock on the first attempt. :raises Timeout: if fails to acquire lock within the timeout period :return: a context object that will unlock the file when the context is exited @@ -172,6 +175,9 @@ def acquire( if self.is_locked: _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) break + elif return_immediately: + _LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename) + raise ImmediateAquireError(self._lock_file) elif 0 <= timeout < time.monotonic() - start_time: _LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename) raise Timeout(self._lock_file) diff --git a/src/filelock/_error.py b/src/filelock/_error.py index b3885214..2590d283 100644 --- a/src/filelock/_error.py +++ b/src/filelock/_error.py @@ -11,7 +11,18 @@ def __init__(self, lock_file: str) -> None: def __str__(self) -> str: return f"The file lock '{self.lock_file}' could not be acquired." +class ImmediateAquireError(TimeoutError): + """Raised when the lock could not be acquired immediately""" + + def __init__(self, lock_file: str) -> None: + #: The path of the file lock. + self.lock_file = lock_file + + def __str__(self) -> str: + return f"The file lock '{self.lock_file}' could not be acquired." + __all__ = [ "Timeout", + "ImmediateAquireError", ] diff --git a/tests/test_filelock.py b/tests/test_filelock.py index f439ff6d..4fdf6ba4 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -14,7 +14,7 @@ import pytest from _pytest.logging import LogCaptureFixture -from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock +from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock, ImmediateAquireError @pytest.mark.parametrize( @@ -259,6 +259,30 @@ def test_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: assert not lock_2.is_locked + +@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) +def test_return_immediately(lock_type: type[BaseFileLock], tmp_path: Path) -> None: + # raises ImmediateAquireError error when the lock cannot be acquired + lock_path = tmp_path / "a" + lock_1, lock_2 = lock_type(str(lock_path)), lock_type(str(lock_path)) + + # acquire lock 1 + lock_1.acquire() + assert lock_1.is_locked + assert not lock_2.is_locked + + # try to acquire lock 2 + with pytest.raises(ImmediateAquireError, match="The file lock '.*' could not be acquired."): + lock_2.acquire(return_immediately=True) + assert not lock_2.is_locked + assert lock_1.is_locked + + # release lock 1 + lock_1.release() + assert not lock_1.is_locked + assert not lock_2.is_locked + + @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) def test_default_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # test if the default timeout parameter works From 99dddba5657e950fa809cefb43ef18226fa0e85a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 May 2022 14:30:15 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/filelock/__init__.py | 2 +- src/filelock/_api.py | 4 ++-- src/filelock/_error.py | 1 + tests/test_filelock.py | 3 +-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/filelock/__init__.py b/src/filelock/__init__.py index 6f1e0d0a..4905d6ae 100644 --- a/src/filelock/__init__.py +++ b/src/filelock/__init__.py @@ -11,7 +11,7 @@ import warnings from ._api import AcquireReturnProxy, BaseFileLock -from ._error import Timeout, ImmediateAquireError +from ._error import ImmediateAquireError, Timeout from ._soft import SoftFileLock from ._unix import UnixFileLock, has_fcntl from ._windows import WindowsFileLock diff --git a/src/filelock/_api.py b/src/filelock/_api.py index e8e619b7..63479b07 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -10,7 +10,7 @@ from types import TracebackType from typing import Any -from ._error import Timeout, ImmediateAquireError +from ._error import ImmediateAquireError, Timeout _LOGGER = logging.getLogger("filelock") @@ -116,7 +116,7 @@ def acquire( poll_interval: float = 0.05, *, poll_intervall: float | None = None, - return_immediately = False + return_immediately=False, ) -> AcquireReturnProxy: """ Try to acquire the file lock. diff --git a/src/filelock/_error.py b/src/filelock/_error.py index 2590d283..c0755f9a 100644 --- a/src/filelock/_error.py +++ b/src/filelock/_error.py @@ -11,6 +11,7 @@ def __init__(self, lock_file: str) -> None: def __str__(self) -> str: return f"The file lock '{self.lock_file}' could not be acquired." + class ImmediateAquireError(TimeoutError): """Raised when the lock could not be acquired immediately""" diff --git a/tests/test_filelock.py b/tests/test_filelock.py index 4fdf6ba4..601a001b 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -14,7 +14,7 @@ import pytest from _pytest.logging import LogCaptureFixture -from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock, ImmediateAquireError +from filelock import BaseFileLock, FileLock, ImmediateAquireError, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock @pytest.mark.parametrize( @@ -259,7 +259,6 @@ def test_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: assert not lock_2.is_locked - @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) def test_return_immediately(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # raises ImmediateAquireError error when the lock cannot be acquired From 66a3cae55826f0225adc5baae2db12dfb0c413c5 Mon Sep 17 00:00:00 2001 From: myheroyuki Date: Wed, 4 May 2022 21:49:05 +0900 Subject: [PATCH 3/6] Updated the name of the return_immediately parameter to the more standard name blocking --- src/filelock/__init__.py | 3 +-- src/filelock/_api.py | 12 ++++++------ src/filelock/_error.py | 12 ------------ tests/test_filelock.py | 10 +++++----- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/filelock/__init__.py b/src/filelock/__init__.py index 4905d6ae..afcdb706 100644 --- a/src/filelock/__init__.py +++ b/src/filelock/__init__.py @@ -11,7 +11,7 @@ import warnings from ._api import AcquireReturnProxy, BaseFileLock -from ._error import ImmediateAquireError, Timeout +from ._error import Timeout from ._soft import SoftFileLock from ._unix import UnixFileLock, has_fcntl from ._windows import WindowsFileLock @@ -45,5 +45,4 @@ "WindowsFileLock", "BaseFileLock", "AcquireReturnProxy", - "ImmediateAquireError", ] diff --git a/src/filelock/_api.py b/src/filelock/_api.py index 63479b07..12dad37e 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -10,7 +10,7 @@ from types import TracebackType from typing import Any -from ._error import ImmediateAquireError, Timeout +from ._error import Timeout _LOGGER = logging.getLogger("filelock") @@ -116,7 +116,7 @@ def acquire( poll_interval: float = 0.05, *, poll_intervall: float | None = None, - return_immediately=False, + blocking=True, ) -> AcquireReturnProxy: """ Try to acquire the file lock. @@ -125,8 +125,8 @@ def acquire( if ``timeout < 0``, there is no timeout and this method will block until the lock could be acquired :param poll_interval: interval of trying to acquire the lock file :param poll_intervall: deprecated, kept for backwards compatibility, use ``poll_interval`` instead - :param return_immediately: defaults to False. If True, function will return immediately if it cannot obtain - a lock on the first attempt. + :param blocking: defaults to True. If False, function will return immediately if it cannot obtain a lock on the + first attempt. Otherwise this method will block until the timeout expires or the lock is acquired. :raises Timeout: if fails to acquire lock within the timeout period :return: a context object that will unlock the file when the context is exited @@ -175,9 +175,9 @@ def acquire( if self.is_locked: _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) break - elif return_immediately: + elif blocking == False: _LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename) - raise ImmediateAquireError(self._lock_file) + raise Timeout(self._lock_file) elif 0 <= timeout < time.monotonic() - start_time: _LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename) raise Timeout(self._lock_file) diff --git a/src/filelock/_error.py b/src/filelock/_error.py index c0755f9a..b3885214 100644 --- a/src/filelock/_error.py +++ b/src/filelock/_error.py @@ -12,18 +12,6 @@ def __str__(self) -> str: return f"The file lock '{self.lock_file}' could not be acquired." -class ImmediateAquireError(TimeoutError): - """Raised when the lock could not be acquired immediately""" - - def __init__(self, lock_file: str) -> None: - #: The path of the file lock. - self.lock_file = lock_file - - def __str__(self) -> str: - return f"The file lock '{self.lock_file}' could not be acquired." - - __all__ = [ "Timeout", - "ImmediateAquireError", ] diff --git a/tests/test_filelock.py b/tests/test_filelock.py index 601a001b..2b212a4b 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -14,7 +14,7 @@ import pytest from _pytest.logging import LogCaptureFixture -from filelock import BaseFileLock, FileLock, ImmediateAquireError, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock +from filelock import BaseFileLock, FileLock, SoftFileLock, Timeout, UnixFileLock, WindowsFileLock @pytest.mark.parametrize( @@ -260,8 +260,8 @@ def test_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_return_immediately(lock_type: type[BaseFileLock], tmp_path: Path) -> None: - # raises ImmediateAquireError error when the lock cannot be acquired +def test_non_blocking(lock_type: type[BaseFileLock], tmp_path: Path) -> None: + # raises Timeout error when the lock cannot be acquired lock_path = tmp_path / "a" lock_1, lock_2 = lock_type(str(lock_path)), lock_type(str(lock_path)) @@ -271,8 +271,8 @@ def test_return_immediately(lock_type: type[BaseFileLock], tmp_path: Path) -> No assert not lock_2.is_locked # try to acquire lock 2 - with pytest.raises(ImmediateAquireError, match="The file lock '.*' could not be acquired."): - lock_2.acquire(return_immediately=True) + with pytest.raises(Timeout, match="The file lock '.*' could not be acquired."): + lock_2.acquire(blocking=False) assert not lock_2.is_locked assert lock_1.is_locked From 8fb79e349ce0c3fb8494520c78e92bc6f6e979d0 Mon Sep 17 00:00:00 2001 From: myheroyuki Date: Wed, 4 May 2022 22:59:23 +0900 Subject: [PATCH 4/6] Fix flake8 issue --- src/filelock/_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filelock/_api.py b/src/filelock/_api.py index 12dad37e..fc629a40 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -175,7 +175,7 @@ def acquire( if self.is_locked: _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) break - elif blocking == False: + elif blocking is False: _LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename) raise Timeout(self._lock_file) elif 0 <= timeout < time.monotonic() - start_time: From 64fbc72c4c8e3ba641149e46da72453cc0ee9619 Mon Sep 17 00:00:00 2001 From: myheroyuki Date: Wed, 11 May 2022 21:30:47 +0900 Subject: [PATCH 5/6] Added type annotation for blocking parameter --- src/filelock/_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/filelock/_api.py b/src/filelock/_api.py index fc629a40..0c02d32f 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -9,6 +9,7 @@ from threading import Lock from types import TracebackType from typing import Any +from xmlrpc.client import Boolean from ._error import Timeout @@ -116,7 +117,7 @@ def acquire( poll_interval: float = 0.05, *, poll_intervall: float | None = None, - blocking=True, + blocking: bool = True, ) -> AcquireReturnProxy: """ Try to acquire the file lock. From 4aea21083bfe13e49cdf3f54b5bc4ca3b931df71 Mon Sep 17 00:00:00 2001 From: myheroyuki Date: Wed, 11 May 2022 21:36:45 +0900 Subject: [PATCH 6/6] Removed line that was auto-added by accident --- src/filelock/_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/filelock/_api.py b/src/filelock/_api.py index 0c02d32f..b7cc7121 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -9,7 +9,6 @@ from threading import Lock from types import TracebackType from typing import Any -from xmlrpc.client import Boolean from ._error import Timeout