diff --git a/src/filelock/_api.py b/src/filelock/_api.py index b074c6d2..2894e61b 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -85,31 +85,24 @@ class BaseFileLock(ABC, contextlib.ContextDecorator): def __new__( # noqa: PLR0913 cls, lock_file: str | os.PathLike[str], - timeout: float = -1, - mode: int = 0o644, - thread_local: bool = True, # noqa: FBT001, FBT002 + timeout: float = -1, # noqa: ARG003 + mode: int = 0o644, # noqa: ARG003 + thread_local: bool = True, # noqa: FBT001, FBT002, ARG003 *, - blocking: bool = True, + blocking: bool = True, # noqa: ARG003 is_singleton: bool = False, **kwargs: Any, # capture remaining kwargs for subclasses # noqa: ARG003, ANN401 ) -> Self: """Create a new lock object or if specified return the singleton instance for the lock file.""" if not is_singleton: - self = super().__new__(cls) - self._initialize(lock_file, timeout, mode, thread_local, blocking=blocking, is_singleton=is_singleton) - return self + return super().__new__(cls) instance = cls._instances.get(str(lock_file)) if not instance: self = super().__new__(cls) - self._initialize(lock_file, timeout, mode, thread_local, blocking=blocking, is_singleton=is_singleton) cls._instances[str(lock_file)] = self return self - if timeout != instance.timeout or mode != instance.mode: - msg = "Singleton lock instances cannot be initialized with differing arguments" - raise ValueError(msg) - return instance # type: ignore[return-value] # https://github.com/python/mypy/issues/15322 def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None: @@ -117,7 +110,7 @@ def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None: super().__init_subclass__(**kwargs) cls._instances = WeakValueDictionary() - def _initialize( # noqa: PLR0913 + def __init__( # noqa: PLR0913 self, lock_file: str | os.PathLike[str], timeout: float = -1, @@ -143,6 +136,34 @@ def _initialize( # noqa: PLR0913 to pass the same object around. """ + if is_singleton and hasattr(self, "_context"): + # test whether other parameters match existing instance. + if not self.is_singleton: + msg = "__init__ should only be called on initialized object if it is a singleton" + raise RuntimeError(msg) + + params_to_check = { + "thread_local": (thread_local, self.is_thread_local()), + "timeout": (timeout, self.timeout), + "mode": (mode, self.mode), + "blocking": (blocking, self.blocking), + } + + non_matching_params = { + name: (passed_param, set_param) + for name, (passed_param, set_param) in params_to_check.items() + if passed_param != set_param + } + if not non_matching_params: + return # bypass initialization because object is already initialized + + # parameters do not match; raise error + msg = "Singleton lock instances cannot be initialized with differing arguments" + msg += "\nNon-matching arguments: " + for param_name, (passed_param, set_param) in non_matching_params.items(): + msg += f"\n\t{param_name} (existing lock has {set_param} but {passed_param} was passed)" + raise ValueError(msg) + self._is_thread_local = thread_local self._is_singleton = is_singleton diff --git a/tests/test_filelock.py b/tests/test_filelock.py index 7d16ae7c..8ecd743b 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -687,9 +687,10 @@ def __init__( # noqa: PLR0913 Too many arguments to function call (6 > 5) mode: int = 0o644, thread_local: bool = True, my_param: int = 0, - **kwargs: dict[str, Any], + **kwargs: dict[str, Any], # noqa: ARG002 ) -> None: - pass + super().__init__(lock_file, timeout, mode, thread_local, blocking=True, is_singleton=True) + self.my_param = my_param lock_path = tmp_path / "a" MyFileLock(str(lock_path), my_param=1) @@ -702,9 +703,10 @@ def __init__( # noqa: PLR0913 Too many arguments to function call (6 > 5) mode: int = 0o644, thread_local: bool = True, my_param: int = 0, - **kwargs: dict[str, Any], + **kwargs: dict[str, Any], # noqa: ARG002 ) -> None: - pass + super().__init__(lock_file, timeout, mode, thread_local, blocking=True, is_singleton=True) + self.my_param = my_param MySoftFileLock(str(lock_path), my_param=1) @@ -742,12 +744,19 @@ def test_singleton_locks_are_distinct_per_lock_file(lock_type: type[BaseFileLock @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) def test_singleton_locks_must_be_initialized_with_the_same_args(lock_type: type[BaseFileLock], tmp_path: Path) -> None: lock_path = tmp_path / "a" - lock = lock_type(str(lock_path), is_singleton=True) # noqa: F841 - - with pytest.raises(ValueError, match="Singleton lock instances cannot be initialized with differing arguments"): - lock_type(str(lock_path), timeout=10, is_singleton=True) - with pytest.raises(ValueError, match="Singleton lock instances cannot be initialized with differing arguments"): - lock_type(str(lock_path), mode=0, is_singleton=True) + args: dict[str, Any] = {"timeout": -1, "mode": 0o644, "thread_local": True, "blocking": True} + alternate_args: dict[str, Any] = {"timeout": 10, "mode": 0, "thread_local": False, "blocking": False} + + lock = lock_type(str(lock_path), is_singleton=True, **args) + + for arg_name in args: + general_msg = "Singleton lock instances cannot be initialized with differing arguments" + altered_args = args.copy() + altered_args[arg_name] = alternate_args[arg_name] + with pytest.raises(ValueError, match=general_msg) as exc_info: + lock_type(str(lock_path), is_singleton=True, **altered_args) + exc_info.match(arg_name) # ensure specific non-matching argument is included in exception text + del lock, exc_info @pytest.mark.skipif(hasattr(sys, "pypy_version_info"), reason="del() does not trigger GC in PyPy") pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy