Update filelock 3.12.0 (b4713c9) → 3.12.4 (c1163ae).

This commit is contained in:
JackDandy 2023-09-17 20:40:34 +01:00
parent d25653b06b
commit 85f196d600
9 changed files with 72 additions and 55 deletions

View file

@ -4,6 +4,7 @@
* Update certifi 2023.05.07 to 2023.07.22
* Update CacheControl 0.12.11 (c05ef9e) to 0.13.1 (783a338)
* Update feedparser 6.0.10 (859ac57) to 6.0.10 (9865dec)
* Update filelock 3.12.0 (b4713c9) to 3.12.4 (c1163ae)
* Update Msgpack 1.0.5 (0516c2c) to 1.0.6 (e1d3d5d)
* Update package resource API 67.5.1 (f51eccd) to 68.1.2 (1ef36f2)
* Update soupsieve 2.3.2.post1 (792d566) to 2.4.1 (2e66beb)

View file

@ -24,7 +24,7 @@ __version__: str = version
if sys.platform == "win32": # pragma: win32 cover
_FileLock: type[BaseFileLock] = WindowsFileLock
else: # pragma: win32 no cover
else: # pragma: win32 no cover # noqa: PLR5501
if has_fcntl:
_FileLock: type[BaseFileLock] = UnixFileLock
else:
@ -32,7 +32,7 @@ else: # pragma: win32 no cover
if warnings is not None:
warnings.warn("only soft file lock is available", stacklevel=2)
if TYPE_CHECKING:
if TYPE_CHECKING: # noqa: SIM108
FileLock = SoftFileLock
else:
#: Alias for the lock, which should be used for the current platform.

View file

@ -8,11 +8,20 @@ import warnings
from abc import ABC, abstractmethod
from dataclasses import dataclass
from threading import local
from types import TracebackType
from typing import Any
from typing import TYPE_CHECKING, Any
from ._error import Timeout
if TYPE_CHECKING:
import sys
from types import TracebackType
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
from typing import Self
else: # pragma: no cover (<py311)
from typing_extensions import Self
_LOGGER = logging.getLogger("filelock")
@ -30,18 +39,16 @@ class AcquireReturnProxy:
def __exit__(
self,
exc_type: type[BaseException] | None, # noqa: U100
exc_value: BaseException | None, # noqa: U100
traceback: TracebackType | None, # noqa: U100
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
self.lock.release()
@dataclass
class FileLockContext:
"""
A dataclass which holds the context for a ``BaseFileLock`` object.
"""
"""A dataclass which holds the context for a ``BaseFileLock`` object."""
# The context is held in a separate class to allow optional use of thread local storage via the
# ThreadLocalFileContext class.
@ -63,9 +70,7 @@ class FileLockContext:
class ThreadLocalFileContext(FileLockContext, local):
"""
A thread local version of the ``FileLockContext`` class.
"""
"""A thread local version of the ``FileLockContext`` class."""
class BaseFileLock(ABC, contextlib.ContextDecorator):
@ -73,10 +78,10 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
def __init__(
self,
lock_file: str | os.PathLike[Any],
lock_file: str | os.PathLike[str],
timeout: float = -1,
mode: int = 0o644,
thread_local: bool = True,
thread_local: bool = True, # noqa: FBT001, FBT002
) -> None:
"""
Create a new lock object.
@ -151,9 +156,7 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
@property
def lock_counter(self) -> int:
"""
:return: The number of times this lock has been acquired (but not yet released).
"""
""":return: The number of times this lock has been acquired (but not yet released)."""
return self._context.lock_counter
def acquire(
@ -218,22 +221,21 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
if self.is_locked:
_LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename)
break
elif blocking is False:
if blocking is False:
_LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename)
raise Timeout(lock_filename)
elif 0 <= timeout < time.perf_counter() - start_time:
raise Timeout(lock_filename) # noqa: TRY301
if 0 <= timeout < time.perf_counter() - start_time:
_LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename)
raise Timeout(lock_filename)
else:
msg = "Lock %s not acquired on %s, waiting %s seconds ..."
_LOGGER.debug(msg, lock_id, lock_filename, poll_interval)
time.sleep(poll_interval)
raise Timeout(lock_filename) # noqa: TRY301
msg = "Lock %s not acquired on %s, waiting %s seconds ..."
_LOGGER.debug(msg, lock_id, lock_filename, poll_interval)
time.sleep(poll_interval)
except BaseException: # Something did go wrong, so decrement the counter.
self._context.lock_counter = max(0, self._context.lock_counter - 1)
raise
return AcquireReturnProxy(lock=self)
def release(self, force: bool = False) -> None:
def release(self, force: bool = False) -> None: # noqa: FBT001, FBT002
"""
Releases the file lock. Please note, that the lock is only completely released, if the lock counter is 0. Also
note, that the lock file itself is not automatically deleted.
@ -251,7 +253,7 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
self._context.lock_counter = 0
_LOGGER.debug("Lock %s released on %s", lock_id, lock_filename)
def __enter__(self) -> BaseFileLock:
def __enter__(self) -> Self:
"""
Acquire the lock.
@ -262,9 +264,9 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
def __exit__(
self,
exc_type: type[BaseException] | None, # noqa: U100
exc_value: BaseException | None, # noqa: U100
traceback: TracebackType | None, # noqa: U100
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
"""
Release the lock.

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any
class Timeout(TimeoutError):
class Timeout(TimeoutError): # noqa: N818
"""Raised when the lock could not be acquired in *timeout* seconds."""
def __init__(self, lock_file: str) -> None:

View file

@ -2,10 +2,12 @@ from __future__ import annotations
import os
import sys
from contextlib import suppress
from errno import EACCES, EEXIST
from pathlib import Path
from ._api import BaseFileLock
from ._util import raise_on_not_writable_file
from ._util import ensure_directory_exists, raise_on_not_writable_file
class SoftFileLock(BaseFileLock):
@ -13,6 +15,7 @@ class SoftFileLock(BaseFileLock):
def _acquire(self) -> None:
raise_on_not_writable_file(self.lock_file)
ensure_directory_exists(self.lock_file)
# first check for exists and read-only mode as the open will mask this case as EEXIST
flags = (
os.O_WRONLY # open for writing only
@ -32,12 +35,11 @@ class SoftFileLock(BaseFileLock):
self._context.lock_file_fd = file_handler
def _release(self) -> None:
os.close(self._context.lock_file_fd) # type: ignore # the lock file is definitely not None
assert self._context.lock_file_fd is not None # noqa: S101
os.close(self._context.lock_file_fd) # the lock file is definitely not None
self._context.lock_file_fd = None
try:
os.remove(self.lock_file)
except OSError: # the file is already deleted and that's what we want
pass
with suppress(OSError): # the file is already deleted and that's what we want
Path(self.lock_file).unlink()
__all__ = [

View file

@ -2,10 +2,12 @@ from __future__ import annotations
import os
import sys
from contextlib import suppress
from errno import ENOSYS
from typing import cast
from ._api import BaseFileLock
from ._util import ensure_directory_exists
#: a flag to indicate if the fcntl API is available
has_fcntl = False
@ -32,18 +34,18 @@ else: # pragma: win32 no cover
"""Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems."""
def _acquire(self) -> None:
ensure_directory_exists(self.lock_file)
open_flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC
fd = os.open(self.lock_file, open_flags, self._context.mode)
try:
with suppress(PermissionError): # This locked is not owned by this UID
os.fchmod(fd, self._context.mode)
except PermissionError:
pass # This locked is not owned by this UID
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as exception:
os.close(fd)
if exception.errno == ENOSYS: # NotImplemented error
raise NotImplementedError("FileSystem does not appear to support flock; user SoftFileLock instead")
msg = "FileSystem does not appear to support flock; user SoftFileLock instead"
raise NotImplementedError(msg) from exception
else:
self._context.lock_file_fd = fd

View file

@ -4,6 +4,7 @@ import os
import stat
import sys
from errno import EACCES, EISDIR
from pathlib import Path
def raise_on_not_writable_file(filename: str) -> None:
@ -12,12 +13,12 @@ def raise_on_not_writable_file(filename: str) -> None:
This is done so files that will never be writable can be separated from
files that are writable but currently locked
:param filename: file to check
:raises OSError: as if the file was opened for writing
:raises OSError: as if the file was opened for writing.
"""
try:
file_stat = os.stat(filename) # use stat to do exists + can write to check without race condition
try: # use stat to do exists + can write to check without race condition
file_stat = os.stat(filename) # noqa: PTH116
except OSError:
return None # swallow does not exist or other errors
return # swallow does not exist or other errors
if file_stat.st_mtime != 0: # if os.stat returns but modification is zero that's an invalid os.stat - ignore it
if not (file_stat.st_mode & stat.S_IWUSR):
@ -27,11 +28,20 @@ def raise_on_not_writable_file(filename: str) -> None:
if sys.platform == "win32": # pragma: win32 cover
# On Windows, this is PermissionError
raise PermissionError(EACCES, "Permission denied", filename)
else: # pragma: win32 no cover
else: # pragma: win32 no cover # noqa: RET506
# On linux / macOS, this is IsADirectoryError
raise IsADirectoryError(EISDIR, "Is a directory", filename)
def ensure_directory_exists(filename: Path | str) -> None:
"""
Ensure the directory containing the file exists (create it if necessary)
:param filename: file.
"""
Path(filename).parent.mkdir(parents=True, exist_ok=True)
__all__ = [
"raise_on_not_writable_file",
"ensure_directory_exists",
]

View file

@ -2,11 +2,13 @@ from __future__ import annotations
import os
import sys
from contextlib import suppress
from errno import EACCES
from pathlib import Path
from typing import cast
from ._api import BaseFileLock
from ._util import raise_on_not_writable_file
from ._util import ensure_directory_exists, raise_on_not_writable_file
if sys.platform == "win32": # pragma: win32 cover
import msvcrt
@ -16,6 +18,7 @@ if sys.platform == "win32": # pragma: win32 cover
def _acquire(self) -> None:
raise_on_not_writable_file(self.lock_file)
ensure_directory_exists(self.lock_file)
flags = (
os.O_RDWR # open for read and write
| os.O_CREAT # create file if not exists
@ -42,11 +45,8 @@ if sys.platform == "win32": # pragma: win32 cover
msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
os.close(fd)
try:
os.remove(self.lock_file)
# Probably another instance of the application hat acquired the file lock.
except OSError:
pass
with suppress(OSError): # Probably another instance of the application hat acquired the file lock.
Path(self.lock_file).unlink()
else: # pragma: win32 no cover

View file

@ -1,4 +1,4 @@
# file generated by setuptools_scm
# don't change, don't track in version control
__version__ = version = '3.12.0'
__version_tuple__ = version_tuple = (3, 12, 0)
__version__ = version = '3.12.4'
__version_tuple__ = version_tuple = (3, 12, 4)