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'

Reply via email to