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 certifi 2023.05.07 to 2023.07.22
* Update CacheControl 0.12.11 (c05ef9e) to 0.13.1 (783a338) * Update CacheControl 0.12.11 (c05ef9e) to 0.13.1 (783a338)
* Update feedparser 6.0.10 (859ac57) to 6.0.10 (9865dec) * 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 Msgpack 1.0.5 (0516c2c) to 1.0.6 (e1d3d5d)
* Update package resource API 67.5.1 (f51eccd) to 68.1.2 (1ef36f2) * 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) * 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 if sys.platform == "win32": # pragma: win32 cover
_FileLock: type[BaseFileLock] = WindowsFileLock _FileLock: type[BaseFileLock] = WindowsFileLock
else: # pragma: win32 no cover else: # pragma: win32 no cover # noqa: PLR5501
if has_fcntl: if has_fcntl:
_FileLock: type[BaseFileLock] = UnixFileLock _FileLock: type[BaseFileLock] = UnixFileLock
else: else:
@ -32,7 +32,7 @@ else: # pragma: win32 no cover
if warnings is not None: if warnings is not None:
warnings.warn("only soft file lock is available", stacklevel=2) warnings.warn("only soft file lock is available", stacklevel=2)
if TYPE_CHECKING: if TYPE_CHECKING: # noqa: SIM108
FileLock = SoftFileLock FileLock = SoftFileLock
else: else:
#: Alias for the lock, which should be used for the current platform. #: 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 abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from threading import local from threading import local
from types import TracebackType from typing import TYPE_CHECKING, Any
from typing import Any
from ._error import Timeout 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") _LOGGER = logging.getLogger("filelock")
@ -30,18 +39,16 @@ class AcquireReturnProxy:
def __exit__( def __exit__(
self, self,
exc_type: type[BaseException] | None, # noqa: U100 exc_type: type[BaseException] | None,
exc_value: BaseException | None, # noqa: U100 exc_value: BaseException | None,
traceback: TracebackType | None, # noqa: U100 traceback: TracebackType | None,
) -> None: ) -> None:
self.lock.release() self.lock.release()
@dataclass @dataclass
class FileLockContext: 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 # The context is held in a separate class to allow optional use of thread local storage via the
# ThreadLocalFileContext class. # ThreadLocalFileContext class.
@ -63,9 +70,7 @@ class FileLockContext:
class ThreadLocalFileContext(FileLockContext, local): 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): class BaseFileLock(ABC, contextlib.ContextDecorator):
@ -73,10 +78,10 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
def __init__( def __init__(
self, self,
lock_file: str | os.PathLike[Any], lock_file: str | os.PathLike[str],
timeout: float = -1, timeout: float = -1,
mode: int = 0o644, mode: int = 0o644,
thread_local: bool = True, thread_local: bool = True, # noqa: FBT001, FBT002
) -> None: ) -> None:
""" """
Create a new lock object. Create a new lock object.
@ -151,9 +156,7 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
@property @property
def lock_counter(self) -> int: 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 return self._context.lock_counter
def acquire( def acquire(
@ -218,22 +221,21 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
if self.is_locked: if self.is_locked:
_LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename)
break break
elif blocking is False: if blocking is False:
_LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename) _LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename)
raise Timeout(lock_filename) raise Timeout(lock_filename) # noqa: TRY301
elif 0 <= timeout < time.perf_counter() - start_time: if 0 <= timeout < time.perf_counter() - start_time:
_LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename) _LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename)
raise Timeout(lock_filename) raise Timeout(lock_filename) # noqa: TRY301
else: msg = "Lock %s not acquired on %s, waiting %s seconds ..."
msg = "Lock %s not acquired on %s, waiting %s seconds ..." _LOGGER.debug(msg, lock_id, lock_filename, poll_interval)
_LOGGER.debug(msg, lock_id, lock_filename, poll_interval) time.sleep(poll_interval)
time.sleep(poll_interval)
except BaseException: # Something did go wrong, so decrement the counter. except BaseException: # Something did go wrong, so decrement the counter.
self._context.lock_counter = max(0, self._context.lock_counter - 1) self._context.lock_counter = max(0, self._context.lock_counter - 1)
raise raise
return AcquireReturnProxy(lock=self) 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 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. note, that the lock file itself is not automatically deleted.
@ -251,7 +253,7 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
self._context.lock_counter = 0 self._context.lock_counter = 0
_LOGGER.debug("Lock %s released on %s", lock_id, lock_filename) _LOGGER.debug("Lock %s released on %s", lock_id, lock_filename)
def __enter__(self) -> BaseFileLock: def __enter__(self) -> Self:
""" """
Acquire the lock. Acquire the lock.
@ -262,9 +264,9 @@ class BaseFileLock(ABC, contextlib.ContextDecorator):
def __exit__( def __exit__(
self, self,
exc_type: type[BaseException] | None, # noqa: U100 exc_type: type[BaseException] | None,
exc_value: BaseException | None, # noqa: U100 exc_value: BaseException | None,
traceback: TracebackType | None, # noqa: U100 traceback: TracebackType | None,
) -> None: ) -> None:
""" """
Release the lock. Release the lock.

View file

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

View file

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

View file

@ -2,10 +2,12 @@ from __future__ import annotations
import os import os
import sys import sys
from contextlib import suppress
from errno import ENOSYS from errno import ENOSYS
from typing import cast from typing import cast
from ._api import BaseFileLock from ._api import BaseFileLock
from ._util import ensure_directory_exists
#: a flag to indicate if the fcntl API is available #: a flag to indicate if the fcntl API is available
has_fcntl = False 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.""" """Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems."""
def _acquire(self) -> None: def _acquire(self) -> None:
ensure_directory_exists(self.lock_file)
open_flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC open_flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC
fd = os.open(self.lock_file, open_flags, self._context.mode) 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) os.fchmod(fd, self._context.mode)
except PermissionError:
pass # This locked is not owned by this UID
try: try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as exception: except OSError as exception:
os.close(fd) os.close(fd)
if exception.errno == ENOSYS: # NotImplemented error 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: else:
self._context.lock_file_fd = fd self._context.lock_file_fd = fd

View file

@ -4,6 +4,7 @@ import os
import stat import stat
import sys import sys
from errno import EACCES, EISDIR from errno import EACCES, EISDIR
from pathlib import Path
def raise_on_not_writable_file(filename: str) -> None: 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 This is done so files that will never be writable can be separated from
files that are writable but currently locked files that are writable but currently locked
:param filename: file to check :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: try: # use stat to do exists + can write to check without race condition
file_stat = os.stat(filename) # use stat to do exists + can write to check without race condition file_stat = os.stat(filename) # noqa: PTH116
except OSError: 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 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): 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 if sys.platform == "win32": # pragma: win32 cover
# On Windows, this is PermissionError # On Windows, this is PermissionError
raise PermissionError(EACCES, "Permission denied", filename) raise PermissionError(EACCES, "Permission denied", filename)
else: # pragma: win32 no cover else: # pragma: win32 no cover # noqa: RET506
# On linux / macOS, this is IsADirectoryError # On linux / macOS, this is IsADirectoryError
raise IsADirectoryError(EISDIR, "Is a directory", filename) 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__ = [ __all__ = [
"raise_on_not_writable_file", "raise_on_not_writable_file",
"ensure_directory_exists",
] ]

View file

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

View file

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