Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-portalocker for openSUSE:Factory checked in at 2024-07-01 11:21:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-portalocker (Old) and /work/SRC/openSUSE:Factory/.python-portalocker.new.18349 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-portalocker" Mon Jul 1 11:21:41 2024 rev:11 rq:1184135 version:2.10.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-portalocker/python-portalocker.changes 2024-01-07 21:42:51.929613721 +0100 +++ /work/SRC/openSUSE:Factory/.python-portalocker.new.18349/python-portalocker.changes 2024-07-01 11:22:31.839777981 +0200 @@ -1,0 +2,8 @@ +Sun Jun 30 20:07:44 UTC 2024 - Dirk Müller <[email protected]> + +- update to 2.10.0: + * Properly propagating exceptions for NFS read-only lock issues + and added support for being able to choose between lockf and + flock + +------------------------------------------------------------------- Old: ---- v2.8.2.tar.gz New: ---- v2.10.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-portalocker.spec ++++++ --- /var/tmp/diff_new_pack.RlxOm5/_old 2024-07-01 11:22:32.695808963 +0200 +++ /var/tmp/diff_new_pack.RlxOm5/_new 2024-07-01 11:22:32.695808963 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-portalocker -Version: 2.8.2 +Version: 2.10.0 Release: 0 Summary: Locking library for Python License: Python-2.0 ++++++ v2.8.2.tar.gz -> v2.10.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/.coveragerc new/portalocker-2.10.0/.coveragerc --- old/portalocker-2.8.2/.coveragerc 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/.coveragerc 2024-06-23 00:48:26.000000000 +0200 @@ -10,6 +10,7 @@ raise NotImplementedError if 0: if __name__ == .__main__.: + typing.Protocol omit = portalocker/redis.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/mypy.ini new/portalocker-2.10.0/mypy.ini --- old/portalocker-2.8.2/mypy.ini 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/mypy.ini 2024-06-23 00:48:26.000000000 +0200 @@ -5,3 +5,4 @@ ignore_missing_imports = True +check_untyped_defs = True \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/__about__.py new/portalocker-2.10.0/portalocker/__about__.py --- old/portalocker-2.8.2/portalocker/__about__.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/__about__.py 2024-06-23 00:48:26.000000000 +0200 @@ -1,6 +1,6 @@ __package_name__ = 'portalocker' __author__ = 'Rick van Hattem' __email__ = '[email protected]' -__version__ = '2.8.2' +__version__ = '2.10.0' __description__ = '''Wraps the portalocker recipe for easy usage''' __url__ = 'https://github.com/WoLpH/portalocker' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/__init__.py new/portalocker-2.10.0/portalocker/__init__.py --- old/portalocker-2.8.2/portalocker/__init__.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/__init__.py 2024-06-23 00:48:26.000000000 +0200 @@ -1,4 +1,11 @@ -from . import __about__, constants, exceptions, portalocker, utils +from . import __about__, constants, exceptions, portalocker +from .utils import ( + BoundedSemaphore, + Lock, + RLock, + TemporaryFileLock, + open_atomic, +) try: # pragma: no cover from .redis import RedisLock @@ -13,7 +20,7 @@ #: Current author's email address __email__ = __about__.__email__ #: Version number -__version__ = '2.8.2' +__version__ = '2.10.0' #: Package description for Pypi __description__ = __about__.__description__ #: Package homepage @@ -52,11 +59,6 @@ #: Locking utility class to automatically handle opening with timeouts and #: context wrappers -Lock = utils.Lock -RLock = utils.RLock -BoundedSemaphore = utils.BoundedSemaphore -TemporaryFileLock = utils.TemporaryFileLock -open_atomic = utils.open_atomic __all__ = [ 'lock', @@ -71,6 +73,7 @@ 'RLock', 'AlreadyLocked', 'BoundedSemaphore', + 'TemporaryFileLock', 'open_atomic', 'RedisLock', ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/__main__.py new/portalocker-2.10.0/portalocker/__main__.py --- old/portalocker-2.8.2/portalocker/__main__.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/__main__.py 2024-06-23 00:48:26.000000000 +0200 @@ -3,13 +3,17 @@ import os import pathlib import re +import typing base_path = pathlib.Path(__file__).parent.parent src_path = base_path / 'portalocker' dist_path = base_path / 'dist' _default_output_path = base_path / 'dist' / 'portalocker.py' -_RELATIVE_IMPORT_RE = re.compile(r'^from \. import (?P<names>.+)$') +_NAMES_RE = re.compile(r'(?P<names>[^()]+)$') +_RELATIVE_IMPORT_RE = re.compile( + r'^from \.(?P<from>.*?) import (?P<paren>\(?)(?P<names>[^()]+)$', +) _USELESS_ASSIGNMENT_RE = re.compile(r'^(?P<name>\w+) = \1\n$') _TEXT_TEMPLATE = """''' @@ -42,18 +46,38 @@ args.func(args) -def _read_file(path, seen_files): +def _read_file(path: pathlib.Path, seen_files: typing.Set[pathlib.Path]): if path in seen_files: return names = set() seen_files.add(path) + paren = False + from_ = None for line in path.open(): - if match := _RELATIVE_IMPORT_RE.match(line): - for name in match.group('names').split(','): - name = name.strip() - names.add(name) - yield from _read_file(src_path / f'{name}.py', seen_files) + if paren: + if ')' in line: + line = line.split(')', 1)[1] + paren = False + continue + + match = _NAMES_RE.match(line) + else: + match = _RELATIVE_IMPORT_RE.match(line) + + if match: + if not paren: + paren = bool(match.group('paren')) + from_ = match.group('from') + + if from_: + names.add(from_) + yield from _read_file(src_path / f'{from_}.py', seen_files) + else: + for name in match.group('names').split(','): + name = name.strip() + names.add(name) + yield from _read_file(src_path / f'{name}.py', seen_files) else: yield _clean_line(line, names) @@ -79,7 +103,7 @@ _TEXT_TEMPLATE.format((base_path / 'LICENSE').read_text()), ) - seen_files = set() + seen_files: typing.Set[pathlib.Path] = set() for line in _read_file(src_path / '__init__.py', seen_files): output_file.write(line) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/constants.py new/portalocker-2.10.0/portalocker/constants.py --- old/portalocker-2.8.2/portalocker/constants.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/constants.py 2024-06-23 00:48:26.000000000 +0200 @@ -14,6 +14,7 @@ - `UNBLOCK` unlock ''' + import enum import os diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/portalocker.py new/portalocker-2.10.0/portalocker/portalocker.py --- old/portalocker-2.8.2/portalocker/portalocker.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/portalocker.py 2024-06-23 00:48:26.000000000 +0200 @@ -1,4 +1,3 @@ -import contextlib import os import typing @@ -9,6 +8,14 @@ LockFlags = constants.LockFlags +class HasFileno(typing.Protocol): + def fileno(self) -> int: ... + + +LOCKER: typing.Optional[typing.Callable[ + [typing.Union[int, HasFileno], int], typing.Any]] = None + + if os.name == 'nt': # pragma: no cover import msvcrt @@ -87,14 +94,18 @@ ) from exc elif os.name == 'posix': # pragma: no cover + import errno import fcntl + # The locking implementation. + # Expected values are either fcntl.flock() or fcntl.lockf(), + # but any callable that matches the syntax will be accepted. + LOCKER = fcntl.flock + def lock(file_: typing.Union[typing.IO, int], flags: LockFlags): - locking_exceptions = (IOError,) - with contextlib.suppress(NameError): - locking_exceptions += (BlockingIOError,) # type: ignore - # Locking with NON_BLOCKING without EXCLUSIVE or SHARED enabled results - # in an error + assert LOCKER is not None, 'We need a locing function in `LOCKER` ' + # Locking with NON_BLOCKING without EXCLUSIVE or SHARED enabled + # results in an error if (flags & LockFlags.NON_BLOCKING) and not flags & ( LockFlags.SHARED | LockFlags.EXCLUSIVE ): @@ -104,14 +115,40 @@ ) try: - fcntl.flock(file_, flags) - except locking_exceptions as exc_value: - # The exception code varies on different systems so we'll catch - # every IO error - raise exceptions.LockException(exc_value, fh=file_) from exc_value + LOCKER(file_, flags) + except OSError as exc_value: + # Python can use one of several different exception classes to + # represent timeout (most likely is BlockingIOError and IOError), + # but these errors may also represent other failures. On some + # systems, `IOError is OSError` which means checking for either + # IOError or OSError can mask other errors. + # The safest check is to catch OSError (from which the others + # inherit) and check the errno (which should be EACCESS or EAGAIN + # according to the spec). + if exc_value.errno in (errno.EACCES, errno.EAGAIN): + # A timeout exception, wrap this so the outer code knows to try + # again (if it wants to). + raise exceptions.AlreadyLocked( + exc_value, + fh=file_, + ) from exc_value + else: + # Something else went wrong; don't wrap this so we stop + # immediately. + raise exceptions.LockException( + exc_value, + fh=file_, + ) from exc_value + except EOFError as exc_value: + # On NFS filesystems, flock can raise an EOFError + raise exceptions.LockException( + exc_value, + fh=file_, + ) from exc_value def unlock(file_: typing.IO): - fcntl.flock(file_.fileno(), LockFlags.UNBLOCK) + assert LOCKER is not None, 'We need a locing function in `LOCKER` ' + LOCKER(file_.fileno(), LockFlags.UNBLOCK) else: # pragma: no cover raise RuntimeError('PortaLocker only defined for nt and posix platforms') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/redis.py new/portalocker-2.10.0/portalocker/redis.py --- old/portalocker-2.8.2/portalocker/redis.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/redis.py 2024-06-23 00:48:26.000000000 +0200 @@ -126,12 +126,12 @@ def client_name(self): return f'{self.channel}-lock' - def acquire( + def acquire( # type: ignore[override] self, timeout: typing.Optional[float] = None, check_interval: typing.Optional[float] = None, fail_when_locked: typing.Optional[bool] = None, - ): + ) -> 'RedisLock': timeout = utils.coalesce(timeout, self.timeout, 0.0) check_interval = utils.coalesce( check_interval, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker/utils.py new/portalocker-2.10.0/portalocker/utils.py --- old/portalocker-2.8.2/portalocker/utils.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker/utils.py 2024-06-23 00:48:26.000000000 +0200 @@ -84,7 +84,7 @@ # `pathlib.Path` cast in case `path` is a `str` path: pathlib.Path = pathlib.Path(filename) - assert not path.exists(), '%r exists' % path + assert not path.exists(), f'{path!r} exists' # Create the parent directory if it doesn't exist path.parent.mkdir(parents=True, exist_ok=True) @@ -132,8 +132,7 @@ timeout: typing.Optional[float] = None, check_interval: typing.Optional[float] = None, fail_when_locked: typing.Optional[bool] = None, - ): - return NotImplemented + ) -> typing.IO[typing.AnyStr]: ... def _timeout_generator( self, @@ -156,10 +155,9 @@ time.sleep(max(0.001, (i * f_check_interval) - since_start_time)) @abc.abstractmethod - def release(self): - return NotImplemented + def release(self): ... - def __enter__(self): + def __enter__(self) -> typing.IO[typing.AnyStr]: return self.acquire() def __exit__( @@ -235,7 +233,7 @@ timeout: typing.Optional[float] = None, check_interval: typing.Optional[float] = None, fail_when_locked: typing.Optional[bool] = None, - ) -> typing.IO: + ) -> typing.IO[typing.AnyStr]: '''Acquire the locked filehandle''' fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked) @@ -281,13 +279,18 @@ if fail_when_locked: try_close() raise exceptions.AlreadyLocked(exception) from exc + except Exception as exc: + # Something went wrong with the locking mechanism. + # Wrap in a LockException and re-raise: + try_close() + raise exceptions.LockException(exc) from exc - # Wait a bit + # Wait a bit if exception: try_close() # We got a timeout... reraising - raise exceptions.LockException(exception) + raise exception # Prepare the filehandle (truncate if needed) fh = self._prepare_fh(fh) @@ -295,6 +298,9 @@ self.fh = fh return fh + def __enter__(self) -> typing.IO[typing.AnyStr]: + return self.acquire() + def release(self): '''Releases the currently locked file handle''' if self.fh: @@ -470,7 +476,7 @@ number=number, ) - def acquire( + def acquire( # type: ignore[override] self, timeout: typing.Optional[float] = None, check_interval: typing.Optional[float] = None, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker_tests/conftest.py new/portalocker-2.10.0/portalocker_tests/conftest.py --- old/portalocker-2.8.2/portalocker_tests/conftest.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker_tests/conftest.py 2024-06-23 00:48:26.000000000 +0200 @@ -5,12 +5,14 @@ import pytest +from portalocker import utils + logger = logging.getLogger(__name__) [email protected] [email protected](scope='function') def tmpfile(tmp_path): - filename = tmp_path / str(random.random()) + filename = tmp_path / str(random.random())[2:] yield str(filename) with contextlib.suppress(PermissionError): filename.unlink(missing_ok=True) @@ -21,3 +23,10 @@ # I'm not a 100% certain this will work correctly unfortunately... there # is some potential for breaking tests multiprocessing.set_start_method('spawn') + + [email protected](autouse=True) +def reduce_timeouts(monkeypatch): + 'For faster testing we reduce the timeouts.' + monkeypatch.setattr(utils, 'DEFAULT_TIMEOUT', 0.1) + monkeypatch.setattr(utils, 'DEFAULT_CHECK_INTERVAL', 0.05) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker_tests/test_combined.py new/portalocker-2.10.0/portalocker_tests/test_combined.py --- old/portalocker-2.8.2/portalocker_tests/test_combined.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker_tests/test_combined.py 2024-06-23 00:48:26.000000000 +0200 @@ -6,6 +6,12 @@ def test_combined(tmpdir): output_file = tmpdir.join('combined.py') __main__.main(['combine', '--output-file', output_file.strpath]) + + print(output_file) # noqa: T201 + print('#################') # noqa: T201 + print(output_file.read()) # noqa: T201 + print('#################') # noqa: T201 + sys.path.append(output_file.dirname) # Combined is being generated above but linters won't understand that import combined # type: ignore diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/portalocker_tests/tests.py new/portalocker-2.10.0/portalocker_tests/tests.py --- old/portalocker-2.8.2/portalocker_tests/tests.py 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/portalocker_tests/tests.py 2024-06-23 00:48:26.000000000 +0200 @@ -1,4 +1,5 @@ import dataclasses +import math import multiprocessing import os import time @@ -7,7 +8,24 @@ import pytest import portalocker -from portalocker import LockFlags, utils +import portalocker.portalocker +from portalocker import LockFlags, exceptions, utils + +if os.name == 'posix': + import fcntl + + LOCKERS = [ + fcntl.flock, + fcntl.lockf, + ] +else: + LOCKERS = [None] # type: ignore + + [email protected] +def locker(request, monkeypatch): + monkeypatch.setattr(portalocker.portalocker, 'LOCKER', request.param) + return request.param def test_exceptions(tmpfile): @@ -43,7 +61,7 @@ print('writing more stuff to my cache...', file=fh) -def test_without_timeout(tmpfile): +def test_without_timeout(tmpfile, monkeypatch): # Open the file 2 times with pytest.raises(portalocker.LockException): with portalocker.Lock(tmpfile, timeout=None) as fh: @@ -160,16 +178,20 @@ def test_exlusive(tmpfile): + text_0 = 'spam and eggs' with open(tmpfile, 'w') as fh: - fh.write('spam and eggs') + fh.write(text_0) with open(tmpfile) as fh: portalocker.lock(fh, portalocker.LOCK_EX | portalocker.LOCK_NB) # Make sure we can't read the locked file - with pytest.raises(portalocker.LockException), open(tmpfile) as fh2: + with pytest.raises(portalocker.LockException), open( + tmpfile, + 'r+', + ) as fh2: portalocker.lock(fh2, portalocker.LOCK_EX | portalocker.LOCK_NB) - fh2.read() + assert fh2.read() == text_0 # Make sure we can't write the locked file with pytest.raises(portalocker.LockException), open( @@ -207,14 +229,15 @@ portalocker.unlock(f) -def test_blocking_timeout(tmpfile): [email protected]('locker', LOCKERS, indirect=True) +def test_blocking_timeout(tmpfile, locker): flags = LockFlags.SHARED with pytest.warns(UserWarning): - with portalocker.Lock(tmpfile, timeout=5, flags=flags): + with portalocker.Lock(tmpfile, 'a+', timeout=5, flags=flags): pass - lock = portalocker.Lock(tmpfile, flags=flags) + lock = portalocker.Lock(tmpfile, 'a+', flags=flags) with pytest.warns(UserWarning): lock.acquire(timeout=5) @@ -223,7 +246,8 @@ os.name == 'nt', reason='Windows uses an entirely different lockmechanism', ) -def test_nonblocking(tmpfile): [email protected]('locker', LOCKERS, indirect=True) +def test_nonblocking(tmpfile, locker): with open(tmpfile, 'w') as fh, pytest.raises(RuntimeError): portalocker.lock(fh, LockFlags.NON_BLOCKING) @@ -272,6 +296,8 @@ filename: str, fail_when_locked: bool, flags: LockFlags, + timeout=0.1, + keep_locked=0.05, ) -> LockResult: # Returns a case of True, False or FileNotFound # https://thedailywtf.com/articles/what_is_truth_0x3f_ @@ -280,11 +306,11 @@ try: with portalocker.Lock( filename, - timeout=0.1, + timeout=timeout, fail_when_locked=fail_when_locked, flags=flags, ): - time.sleep(0.2) + time.sleep(keep_locked) return LockResult() except Exception as exception: @@ -300,39 +326,77 @@ @pytest.mark.parametrize('fail_when_locked', [True, False]) def test_shared_processes(tmpfile, fail_when_locked): flags = LockFlags.SHARED | LockFlags.NON_BLOCKING + print() + print(f'{fail_when_locked=}, {flags=}, {os.name=}, {LOCKERS=}') with multiprocessing.Pool(processes=2) as pool: args = tmpfile, fail_when_locked, flags results = pool.starmap_async(lock, 2 * [args]) - for result in results.get(timeout=3): + # sourcery skip: no-loop-in-tests + for result in results.get(timeout=1.0): + print(f'{result=}') + # sourcery skip: no-conditionals-in-tests + if result.exception_class is not None: + raise result.exception_class assert result == LockResult() @pytest.mark.parametrize('fail_when_locked', [True, False]) -def test_exclusive_processes(tmpfile, fail_when_locked): [email protected]('locker', LOCKERS, indirect=True) +def test_exclusive_processes(tmpfile: str, fail_when_locked: bool, locker): flags = LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING + print('Locking', tmpfile, fail_when_locked, locker) with multiprocessing.Pool(processes=2) as pool: - # filename, fail_when_locked, flags - args = tmpfile, fail_when_locked, flags - a, b = pool.starmap_async(lock, 2 * [args]).get(timeout=3) - - assert not a.exception_class or not b.exception_class - assert issubclass( - a.exception_class or b.exception_class, # type: ignore - portalocker.LockException, - ) + # Submit tasks individually + result_a = pool.apply_async(lock, [tmpfile, fail_when_locked, flags]) + result_b = pool.apply_async(lock, [tmpfile, fail_when_locked, flags]) + + try: + a = result_a.get(timeout=1.0) # Wait for 'a' with timeout + except multiprocessing.TimeoutError: + a = None + + try: + # Lower timeout since we already waited with `a` + b = result_b.get(timeout=0.1) # Wait for 'b' with timeout + except multiprocessing.TimeoutError: + b = None + + assert a or b + # Make sure a is always filled + if b: + b, a = b, a + + print(f'{a=}') + print(f'{b=}') + + # make pyright happy + assert a is not None + + if b: + # make pyright happy + assert b is not None + + assert not a.exception_class or not b.exception_class + assert issubclass( + a.exception_class or b.exception_class, # type: ignore + portalocker.LockException, + ) + else: + assert not a.exception_class @pytest.mark.skipif( os.name == 'nt', reason='Locking on Windows requires a file object', ) -def test_lock_fileno(tmpfile): - with open(tmpfile, 'a') as a: - with open(tmpfile, 'a') as b: - # Lock exclusive non-blocking [email protected]('locker', LOCKERS, indirect=True) +def test_lock_fileno(tmpfile, locker): + with open(tmpfile, 'a+') as a: + with open(tmpfile, 'a+') as b: + # Lock shared non-blocking flags = LockFlags.SHARED | LockFlags.NON_BLOCKING # First lock file a @@ -340,3 +404,43 @@ # Now see if we can lock using fileno() portalocker.lock(b.fileno(), flags) + + [email protected]( + os.name != 'posix', + reason='Only posix systems have different lockf behaviour', +) [email protected]('locker', LOCKERS, indirect=True) +def test_locker_mechanism(tmpfile, locker): + '''Can we switch the locking mechanism?''' + # We can test for flock vs lockf based on their different behaviour re. + # locking the same file. + with portalocker.Lock(tmpfile, 'a+', flags=LockFlags.EXCLUSIVE): + # If we have lockf(), we cannot get another lock on the same file. + if locker is fcntl.lockf: + portalocker.Lock( + tmpfile, + 'r+', + flags=LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING, + ).acquire(timeout=0.1) + # But with other lock methods we can't + else: + with pytest.raises(portalocker.LockException): + portalocker.Lock( + tmpfile, + 'r+', + flags=LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING, + ).acquire(timeout=0.1) + + +def test_exception(monkeypatch, tmpfile): + '''Do we stop immediately if the locking fails, even with a timeout?''' + + def patched_lock(*args, **kwargs): + raise ValueError('Test exception') + + monkeypatch.setattr('portalocker.utils.portalocker.lock', patched_lock) + lock = portalocker.Lock(tmpfile, 'w', timeout=math.inf) + + with pytest.raises(exceptions.LockException): + lock.acquire() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/pyproject.toml new/portalocker-2.10.0/pyproject.toml --- old/portalocker-2.8.2/pyproject.toml 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/pyproject.toml 2024-06-23 00:48:26.000000000 +0200 @@ -1,6 +1,6 @@ [build-system] build-backend = 'setuptools.build_meta' -requires = ['setuptools', 'setuptools-scm', 'wheel'] +requires = ['setuptools', 'setuptools-scm'] [project] name = 'portalocker' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/portalocker-2.8.2/ruff.toml new/portalocker-2.10.0/ruff.toml --- old/portalocker-2.8.2/ruff.toml 2023-09-16 16:57:53.000000000 +0200 +++ new/portalocker-2.10.0/ruff.toml 2024-06-23 00:48:26.000000000 +0200 @@ -6,7 +6,9 @@ src = ['portalocker'] exclude = ['docs'] -format = 'grouped' +line-length = 80 + +[lint] ignore = [ 'A001', # Variable {name} is shadowing a Python builtin 'A002', # Argument {name} is shadowing a Python builtin @@ -26,7 +28,6 @@ 'FA100', # Missing `from __future__ import annotations`, but uses `typing.Optional` ] -line-length = 80 select = [ 'A', # flake8-builtins 'ASYNC', # flake8 async checker @@ -60,19 +61,19 @@ 'UP', # pyupgrade ] -[per-file-ignores] -'portalocker_tests/tests.py' = ['SIM115', 'SIM117'] +[lint.per-file-ignores] +'portalocker_tests/tests.py' = ['SIM115', 'SIM117', 'T201'] -[pydocstyle] +[lint.pydocstyle] convention = 'google' ignore-decorators = ['typing.overload'] -[isort] +[lint.isort] case-sensitive = true combine-as-imports = true force-wrap-aliases = true -[flake8-quotes] +[lint.flake8-quotes] docstring-quotes = 'single' inline-quotes = 'single' multiline-quotes = 'single'
