New Upstream Release - portalocker

Ready changes

Summary

Merged new upstream version: 2.6.0 (was: 2.2.1).

Resulting package

Built on 2023-01-05T12:14 (took 5m23s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-portalocker

Lintian Result

Diff

diff --git a/LICENSE b/LICENSE
index adb8038..b638bda 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,48 +1,11 @@
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
+Copyright 2022 Rick van Hattem
 
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
-2. Subject to the terms and conditions of this License Agreement, PSF hereby
-grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
-analyze, test, perform and/or display publicly, prepare derivative works,
-distribute, and otherwise use Python alone or in any derivative version,
-provided, however, that PSF's License Agreement and PSF's notice of copyright,
-i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
-Python Software Foundation; All Rights Reserved" are retained in Python alone or
-in any derivative version prepared by Licensee.
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 
-4. PSF is making Python available to Licensee on an "AS IS"
-basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee.  This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PKG-INFO b/PKG-INFO
index 8c1fa9c..bd6214c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,228 +1,212 @@
 Metadata-Version: 2.1
 Name: portalocker
-Version: 2.2.1
+Version: 2.6.0
 Summary: Wraps the portalocker recipe for easy usage
 Home-page: https://github.com/WoLpH/portalocker
 Author: Rick van Hattem
 Author-email: wolph@wol.ph
 License: PSF
-Description: ############################################
-        portalocker - Cross-platform locking library
-        ############################################
-        
-        .. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
-            :alt: Linux Test Status
-            :target: https://travis-ci.com/WoLpH/portalocker
-        
-        .. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
-            :alt: Windows Tests Status
-            :target: https://ci.appveyor.com/project/WoLpH/portalocker
-        
-        .. image:: https://coveralls.io/repos/WoLpH/portalocker/badge.svg?branch=master
-            :alt: Coverage Status
-            :target: https://coveralls.io/r/WoLpH/portalocker?branch=master
-        
-        Overview
-        --------
-        
-        Portalocker is a library to provide an easy API to file locking.
-        
-        An important detail to note is that on Linux and Unix systems the locks are
-        advisory by default. By specifying the `-o mand` option to the mount command it
-        is possible to enable mandatory file locking on Linux. This is generally not
-        recommended however. For more information about the subject:
-        
-         - https://en.wikipedia.org/wiki/File_locking
-         - http://stackoverflow.com/questions/39292051/portalocker-does-not-seem-to-lock
-         - https://stackoverflow.com/questions/12062466/mandatory-file-lock-on-linux
-        
-        The module is currently maintained by Rick van Hattem <Wolph@wol.ph>.
-        The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
-        requests can be submitted there. Patches are also very welcome.
-        
-        Redis Locks
-        -----------
-        
-        This library now features a lock based on Redis which allows for locks across
-        multiple threads, processes and even distributed locks across multiple
-        computers.
-        
-        It is an extremely reliable Redis lock that is based on pubsub.
-        
-        As opposed to most Redis locking systems based on key/value pairs,
-        this locking method is based on the pubsub system. The big advantage is
-        that if the connection gets killed due to network issues, crashing
-        processes or otherwise, it will still immediately unlock instead of
-        waiting for a lock timeout.
-        
-        Usage is really easy:
-        
-        ::
-        
-            import portalocker
-        
-            lock = portalocker.RedisLock('some_lock_channel_name')
-        
-            with lock:
-                print('do something here')
-        
-        The API is essentially identical to the other ``Lock`` classes so in addition
-        to the ``with`` statement you can also use ``lock.acquire(...)``.
-        
-        Python 2
-        --------
-        
-        Python 2 was supported in versions before Portalocker 2.0. If you are still
-        using
-        Python 2,
-        you can run this to install:
-        
-        ::
-        
-            pip install "portalocker<2"
-        
-        Tips
-        ----
-        
-        On some networked filesystems it might be needed to force a `os.fsync()` before
-        closing the file so it's actually written before another client reads the file.
-        Effectively this comes down to:
-        
-        ::
-        
-           with portalocker.Lock('some_file', 'rb+', timeout=60) as fh:
-               # do what you need to do
-               ...
-        
-               # flush and sync to filesystem
-               fh.flush()
-               os.fsync(fh.fileno())
-        
-        Links
-        -----
-        
-        * Documentation
-            - http://portalocker.readthedocs.org/en/latest/
-        * Source
-            - https://github.com/WoLpH/portalocker
-        * Bug reports
-            - https://github.com/WoLpH/portalocker/issues
-        * Package homepage
-            - https://pypi.python.org/pypi/portalocker
-        * My blog
-            - http://w.wol.ph/
-        
-        Examples
-        --------
-        
-        To make sure your cache generation scripts don't race, use the `Lock` class:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.EXCLUSIVE)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.EXCLUSIVE)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.LOCK_EX)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
-        
-        If needed, it can be done through:
-        
-        >>> portalocker.unlock(file)
-        
-        Do note that your data might still be in a buffer so it is possible that your
-        data is not available until you `flush()` or `close()`.
-        
-        To create a cross platform bounded semaphore across multiple processes you can
-        use the `BoundedSemaphore` class which functions somewhat similar to
-        `threading.BoundedSemaphore`:
-        
-        >>> import portalocker
-        >>> n = 2
-        >>> timeout = 0.1
-        
-        >>> semaphore_a = portalocker.BoundedSemaphore(n, timeout=timeout)
-        >>> semaphore_b = portalocker.BoundedSemaphore(n, timeout=timeout)
-        >>> semaphore_c = portalocker.BoundedSemaphore(n, timeout=timeout)
-        
-        >>> semaphore_a.acquire()
-        <portalocker.utils.Lock object at ...>
-        >>> semaphore_b.acquire()
-        <portalocker.utils.Lock object at ...>
-        >>> semaphore_c.acquire()
-        Traceback (most recent call last):
-          ...
-        portalocker.exceptions.AlreadyLocked
-        
-        
-        More examples can be found in the
-        `tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
-        
-        Changelog
-        ---------
-        
-        Every release has a ``git tag`` with a commit message for the tag
-        explaining what was added and/or changed. The list of tags/releases
-        including the commit messages can be found here:
-        https://github.com/WoLpH/portalocker/releases
-        
-        License
-        -------
-        
-        See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
-        
-        
 Keywords: locking,locks,with statement,windows,linux,unix
 Platform: any
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.5
 Provides-Extra: docs
 Provides-Extra: tests
 Provides-Extra: redis
+License-File: LICENSE
+
+############################################
+portalocker - Cross-platform locking library
+############################################
+
+.. image:: https://github.com/WoLpH/portalocker/actions/workflows/python-package.yml/badge.svg?branch=master
+    :alt: Linux Test Status
+    :target: https://github.com/WoLpH/portalocker/actions/
+
+.. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
+    :alt: Windows Tests Status
+    :target: https://ci.appveyor.com/project/WoLpH/portalocker
+
+.. image:: https://coveralls.io/repos/WoLpH/portalocker/badge.svg?branch=master
+    :alt: Coverage Status
+    :target: https://coveralls.io/r/WoLpH/portalocker?branch=master
+
+Overview
+--------
+
+Portalocker is a library to provide an easy API to file locking.
+
+An important detail to note is that on Linux and Unix systems the locks are
+advisory by default. By specifying the `-o mand` option to the mount command it
+is possible to enable mandatory file locking on Linux. This is generally not
+recommended however. For more information about the subject:
+
+ - https://en.wikipedia.org/wiki/File_locking
+ - http://stackoverflow.com/questions/39292051/portalocker-does-not-seem-to-lock
+ - https://stackoverflow.com/questions/12062466/mandatory-file-lock-on-linux
+
+The module is currently maintained by Rick van Hattem <Wolph@wol.ph>.
+The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
+requests can be submitted there. Patches are also very welcome.
+
+Redis Locks
+-----------
+
+This library now features a lock based on Redis which allows for locks across
+multiple threads, processes and even distributed locks across multiple
+computers.
+
+It is an extremely reliable Redis lock that is based on pubsub.
+
+As opposed to most Redis locking systems based on key/value pairs,
+this locking method is based on the pubsub system. The big advantage is
+that if the connection gets killed due to network issues, crashing
+processes or otherwise, it will still immediately unlock instead of
+waiting for a lock timeout.
+
+First make sure you have everything installed correctly:
+
+::
+
+    pip install "portalocker[redis]"
+
+Usage is really easy:
+
+::
+
+    import portalocker
+
+    lock = portalocker.RedisLock('some_lock_channel_name')
+
+    with lock:
+        print('do something here')
+
+The API is essentially identical to the other ``Lock`` classes so in addition
+to the ``with`` statement you can also use ``lock.acquire(...)``.
+
+Python 2
+--------
+
+Python 2 was supported in versions before Portalocker 2.0. If you are still
+using
+Python 2,
+you can run this to install:
+
+::
+
+    pip install "portalocker<2"
+
+Tips
+----
+
+On some networked filesystems it might be needed to force a `os.fsync()` before
+closing the file so it's actually written before another client reads the file.
+Effectively this comes down to:
+
+::
+
+   with portalocker.Lock('some_file', 'rb+', timeout=60) as fh:
+       # do what you need to do
+       ...
+
+       # flush and sync to filesystem
+       fh.flush()
+       os.fsync(fh.fileno())
+
+Links
+-----
+
+* Documentation
+    - http://portalocker.readthedocs.org/en/latest/
+* Source
+    - https://github.com/WoLpH/portalocker
+* Bug reports
+    - https://github.com/WoLpH/portalocker/issues
+* Package homepage
+    - https://pypi.python.org/pypi/portalocker
+* My blog
+    - http://w.wol.ph/
+
+Examples
+--------
+
+To make sure your cache generation scripts don't race, use the `Lock` class:
+
+>>> import portalocker
+>>> with portalocker.Lock('somefile', timeout=1) as fh:
+...     print('writing some stuff to my cache...', file=fh)
+
+To customize the opening and locking a manual approach is also possible:
+
+>>> import portalocker
+>>> file = open('somefile', 'r+')
+>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
+>>> file.seek(12)
+>>> file.write('foo')
+>>> file.close()
+
+Explicitly unlocking is not needed in most cases but omitting it has been known
+to cause issues:
+https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
+
+If needed, it can be done through:
+
+>>> portalocker.unlock(file)
+
+Do note that your data might still be in a buffer so it is possible that your
+data is not available until you `flush()` or `close()`.
+
+To create a cross platform bounded semaphore across multiple processes you can
+use the `BoundedSemaphore` class which functions somewhat similar to
+`threading.BoundedSemaphore`:
+
+>>> import portalocker
+>>> n = 2
+>>> timeout = 0.1
+
+>>> semaphore_a = portalocker.BoundedSemaphore(n, timeout=timeout)
+>>> semaphore_b = portalocker.BoundedSemaphore(n, timeout=timeout)
+>>> semaphore_c = portalocker.BoundedSemaphore(n, timeout=timeout)
+
+>>> semaphore_a.acquire()
+<portalocker.utils.Lock object at ...>
+>>> semaphore_b.acquire()
+<portalocker.utils.Lock object at ...>
+>>> semaphore_c.acquire()
+Traceback (most recent call last):
+  ...
+portalocker.exceptions.AlreadyLocked
+
+
+More examples can be found in the
+`tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
+
+
+Versioning
+----------
+
+This library follows `Semantic Versioning <http://semver.org/>`_.
+
+
+Changelog
+---------
+
+Every release has a ``git tag`` with a commit message for the tag
+explaining what was added and/or changed. The list of tags/releases
+including the commit messages can be found here:
+https://github.com/WoLpH/portalocker/releases
+
+License
+-------
+
+See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
+
diff --git a/README.rst b/README.rst
index 52a43e0..9b363c2 100644
--- a/README.rst
+++ b/README.rst
@@ -2,9 +2,9 @@
 portalocker - Cross-platform locking library
 ############################################
 
-.. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
+.. image:: https://github.com/WoLpH/portalocker/actions/workflows/python-package.yml/badge.svg?branch=master
     :alt: Linux Test Status
-    :target: https://travis-ci.com/WoLpH/portalocker
+    :target: https://github.com/WoLpH/portalocker/actions/
 
 .. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
     :alt: Windows Tests Status
@@ -47,6 +47,12 @@ that if the connection gets killed due to network issues, crashing
 processes or otherwise, it will still immediately unlock instead of
 waiting for a lock timeout.
 
+First make sure you have everything installed correctly:
+
+::
+
+    pip install "portalocker[redis]"
+
 Usage is really easy:
 
 ::
@@ -111,45 +117,13 @@ To make sure your cache generation scripts don't race, use the `Lock` class:
 
 >>> import portalocker
 >>> with portalocker.Lock('somefile', timeout=1) as fh:
-...     print >>fh, 'writing some stuff to my cache...'
-
-To customize the opening and locking a manual approach is also possible:
-
->>> import portalocker
->>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.EXCLUSIVE)
->>> file.seek(12)
->>> file.write('foo')
->>> file.close()
-
-Explicitly unlocking is not needed in most cases but omitting it has been known
-to cause issues:
-
->>> import portalocker
->>> with portalocker.Lock('somefile', timeout=1) as fh:
-...     print >>fh, 'writing some stuff to my cache...'
+...     print('writing some stuff to my cache...', file=fh)
 
 To customize the opening and locking a manual approach is also possible:
 
 >>> import portalocker
 >>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.EXCLUSIVE)
->>> file.seek(12)
->>> file.write('foo')
->>> file.close()
-
-Explicitly unlocking is not needed in most cases but omitting it has been known
-to cause issues:
-
->>> import portalocker
->>> with portalocker.Lock('somefile', timeout=1) as fh:
-...     print >>fh, 'writing some stuff to my cache...'
-
-To customize the opening and locking a manual approach is also possible:
-
->>> import portalocker
->>> file = open('somefile', 'r+')
->>> portalocker.lock(file, portalocker.LOCK_EX)
+>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
 >>> file.seek(12)
 >>> file.write('foo')
 >>> file.close()
@@ -190,6 +164,13 @@ portalocker.exceptions.AlreadyLocked
 More examples can be found in the
 `tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
 
+
+Versioning
+----------
+
+This library follows `Semantic Versioning <http://semver.org/>`_.
+
+
 Changelog
 ---------
 
diff --git a/debian/changelog b/debian/changelog
index eb9e633..bec51ab 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-portalocker (2.2.1-2) UNRELEASED; urgency=medium
+portalocker (2.6.0-1) UNRELEASED; urgency=medium
 
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Thu, 05 Jan 2023 05:47:20 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 05 Jan 2023 12:10:01 -0000
 
 portalocker (2.2.1-1) unstable; urgency=medium
 
diff --git a/portalocker.egg-info/PKG-INFO b/portalocker.egg-info/PKG-INFO
index 8c1fa9c..bd6214c 100644
--- a/portalocker.egg-info/PKG-INFO
+++ b/portalocker.egg-info/PKG-INFO
@@ -1,228 +1,212 @@
 Metadata-Version: 2.1
 Name: portalocker
-Version: 2.2.1
+Version: 2.6.0
 Summary: Wraps the portalocker recipe for easy usage
 Home-page: https://github.com/WoLpH/portalocker
 Author: Rick van Hattem
 Author-email: wolph@wol.ph
 License: PSF
-Description: ############################################
-        portalocker - Cross-platform locking library
-        ############################################
-        
-        .. image:: https://travis-ci.com/WoLpH/portalocker.svg?branch=master
-            :alt: Linux Test Status
-            :target: https://travis-ci.com/WoLpH/portalocker
-        
-        .. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
-            :alt: Windows Tests Status
-            :target: https://ci.appveyor.com/project/WoLpH/portalocker
-        
-        .. image:: https://coveralls.io/repos/WoLpH/portalocker/badge.svg?branch=master
-            :alt: Coverage Status
-            :target: https://coveralls.io/r/WoLpH/portalocker?branch=master
-        
-        Overview
-        --------
-        
-        Portalocker is a library to provide an easy API to file locking.
-        
-        An important detail to note is that on Linux and Unix systems the locks are
-        advisory by default. By specifying the `-o mand` option to the mount command it
-        is possible to enable mandatory file locking on Linux. This is generally not
-        recommended however. For more information about the subject:
-        
-         - https://en.wikipedia.org/wiki/File_locking
-         - http://stackoverflow.com/questions/39292051/portalocker-does-not-seem-to-lock
-         - https://stackoverflow.com/questions/12062466/mandatory-file-lock-on-linux
-        
-        The module is currently maintained by Rick van Hattem <Wolph@wol.ph>.
-        The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
-        requests can be submitted there. Patches are also very welcome.
-        
-        Redis Locks
-        -----------
-        
-        This library now features a lock based on Redis which allows for locks across
-        multiple threads, processes and even distributed locks across multiple
-        computers.
-        
-        It is an extremely reliable Redis lock that is based on pubsub.
-        
-        As opposed to most Redis locking systems based on key/value pairs,
-        this locking method is based on the pubsub system. The big advantage is
-        that if the connection gets killed due to network issues, crashing
-        processes or otherwise, it will still immediately unlock instead of
-        waiting for a lock timeout.
-        
-        Usage is really easy:
-        
-        ::
-        
-            import portalocker
-        
-            lock = portalocker.RedisLock('some_lock_channel_name')
-        
-            with lock:
-                print('do something here')
-        
-        The API is essentially identical to the other ``Lock`` classes so in addition
-        to the ``with`` statement you can also use ``lock.acquire(...)``.
-        
-        Python 2
-        --------
-        
-        Python 2 was supported in versions before Portalocker 2.0. If you are still
-        using
-        Python 2,
-        you can run this to install:
-        
-        ::
-        
-            pip install "portalocker<2"
-        
-        Tips
-        ----
-        
-        On some networked filesystems it might be needed to force a `os.fsync()` before
-        closing the file so it's actually written before another client reads the file.
-        Effectively this comes down to:
-        
-        ::
-        
-           with portalocker.Lock('some_file', 'rb+', timeout=60) as fh:
-               # do what you need to do
-               ...
-        
-               # flush and sync to filesystem
-               fh.flush()
-               os.fsync(fh.fileno())
-        
-        Links
-        -----
-        
-        * Documentation
-            - http://portalocker.readthedocs.org/en/latest/
-        * Source
-            - https://github.com/WoLpH/portalocker
-        * Bug reports
-            - https://github.com/WoLpH/portalocker/issues
-        * Package homepage
-            - https://pypi.python.org/pypi/portalocker
-        * My blog
-            - http://w.wol.ph/
-        
-        Examples
-        --------
-        
-        To make sure your cache generation scripts don't race, use the `Lock` class:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.EXCLUSIVE)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.EXCLUSIVE)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        
-        >>> import portalocker
-        >>> with portalocker.Lock('somefile', timeout=1) as fh:
-        ...     print >>fh, 'writing some stuff to my cache...'
-        
-        To customize the opening and locking a manual approach is also possible:
-        
-        >>> import portalocker
-        >>> file = open('somefile', 'r+')
-        >>> portalocker.lock(file, portalocker.LOCK_EX)
-        >>> file.seek(12)
-        >>> file.write('foo')
-        >>> file.close()
-        
-        Explicitly unlocking is not needed in most cases but omitting it has been known
-        to cause issues:
-        https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
-        
-        If needed, it can be done through:
-        
-        >>> portalocker.unlock(file)
-        
-        Do note that your data might still be in a buffer so it is possible that your
-        data is not available until you `flush()` or `close()`.
-        
-        To create a cross platform bounded semaphore across multiple processes you can
-        use the `BoundedSemaphore` class which functions somewhat similar to
-        `threading.BoundedSemaphore`:
-        
-        >>> import portalocker
-        >>> n = 2
-        >>> timeout = 0.1
-        
-        >>> semaphore_a = portalocker.BoundedSemaphore(n, timeout=timeout)
-        >>> semaphore_b = portalocker.BoundedSemaphore(n, timeout=timeout)
-        >>> semaphore_c = portalocker.BoundedSemaphore(n, timeout=timeout)
-        
-        >>> semaphore_a.acquire()
-        <portalocker.utils.Lock object at ...>
-        >>> semaphore_b.acquire()
-        <portalocker.utils.Lock object at ...>
-        >>> semaphore_c.acquire()
-        Traceback (most recent call last):
-          ...
-        portalocker.exceptions.AlreadyLocked
-        
-        
-        More examples can be found in the
-        `tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
-        
-        Changelog
-        ---------
-        
-        Every release has a ``git tag`` with a commit message for the tag
-        explaining what was added and/or changed. The list of tags/releases
-        including the commit messages can be found here:
-        https://github.com/WoLpH/portalocker/releases
-        
-        License
-        -------
-        
-        See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
-        
-        
 Keywords: locking,locks,with statement,windows,linux,unix
 Platform: any
 Classifier: Intended Audience :: Developers
 Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.3
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Python: >=3.5
 Provides-Extra: docs
 Provides-Extra: tests
 Provides-Extra: redis
+License-File: LICENSE
+
+############################################
+portalocker - Cross-platform locking library
+############################################
+
+.. image:: https://github.com/WoLpH/portalocker/actions/workflows/python-package.yml/badge.svg?branch=master
+    :alt: Linux Test Status
+    :target: https://github.com/WoLpH/portalocker/actions/
+
+.. image:: https://ci.appveyor.com/api/projects/status/mgqry98hgpy4prhh?svg=true
+    :alt: Windows Tests Status
+    :target: https://ci.appveyor.com/project/WoLpH/portalocker
+
+.. image:: https://coveralls.io/repos/WoLpH/portalocker/badge.svg?branch=master
+    :alt: Coverage Status
+    :target: https://coveralls.io/r/WoLpH/portalocker?branch=master
+
+Overview
+--------
+
+Portalocker is a library to provide an easy API to file locking.
+
+An important detail to note is that on Linux and Unix systems the locks are
+advisory by default. By specifying the `-o mand` option to the mount command it
+is possible to enable mandatory file locking on Linux. This is generally not
+recommended however. For more information about the subject:
+
+ - https://en.wikipedia.org/wiki/File_locking
+ - http://stackoverflow.com/questions/39292051/portalocker-does-not-seem-to-lock
+ - https://stackoverflow.com/questions/12062466/mandatory-file-lock-on-linux
+
+The module is currently maintained by Rick van Hattem <Wolph@wol.ph>.
+The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
+requests can be submitted there. Patches are also very welcome.
+
+Redis Locks
+-----------
+
+This library now features a lock based on Redis which allows for locks across
+multiple threads, processes and even distributed locks across multiple
+computers.
+
+It is an extremely reliable Redis lock that is based on pubsub.
+
+As opposed to most Redis locking systems based on key/value pairs,
+this locking method is based on the pubsub system. The big advantage is
+that if the connection gets killed due to network issues, crashing
+processes or otherwise, it will still immediately unlock instead of
+waiting for a lock timeout.
+
+First make sure you have everything installed correctly:
+
+::
+
+    pip install "portalocker[redis]"
+
+Usage is really easy:
+
+::
+
+    import portalocker
+
+    lock = portalocker.RedisLock('some_lock_channel_name')
+
+    with lock:
+        print('do something here')
+
+The API is essentially identical to the other ``Lock`` classes so in addition
+to the ``with`` statement you can also use ``lock.acquire(...)``.
+
+Python 2
+--------
+
+Python 2 was supported in versions before Portalocker 2.0. If you are still
+using
+Python 2,
+you can run this to install:
+
+::
+
+    pip install "portalocker<2"
+
+Tips
+----
+
+On some networked filesystems it might be needed to force a `os.fsync()` before
+closing the file so it's actually written before another client reads the file.
+Effectively this comes down to:
+
+::
+
+   with portalocker.Lock('some_file', 'rb+', timeout=60) as fh:
+       # do what you need to do
+       ...
+
+       # flush and sync to filesystem
+       fh.flush()
+       os.fsync(fh.fileno())
+
+Links
+-----
+
+* Documentation
+    - http://portalocker.readthedocs.org/en/latest/
+* Source
+    - https://github.com/WoLpH/portalocker
+* Bug reports
+    - https://github.com/WoLpH/portalocker/issues
+* Package homepage
+    - https://pypi.python.org/pypi/portalocker
+* My blog
+    - http://w.wol.ph/
+
+Examples
+--------
+
+To make sure your cache generation scripts don't race, use the `Lock` class:
+
+>>> import portalocker
+>>> with portalocker.Lock('somefile', timeout=1) as fh:
+...     print('writing some stuff to my cache...', file=fh)
+
+To customize the opening and locking a manual approach is also possible:
+
+>>> import portalocker
+>>> file = open('somefile', 'r+')
+>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
+>>> file.seek(12)
+>>> file.write('foo')
+>>> file.close()
+
+Explicitly unlocking is not needed in most cases but omitting it has been known
+to cause issues:
+https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
+
+If needed, it can be done through:
+
+>>> portalocker.unlock(file)
+
+Do note that your data might still be in a buffer so it is possible that your
+data is not available until you `flush()` or `close()`.
+
+To create a cross platform bounded semaphore across multiple processes you can
+use the `BoundedSemaphore` class which functions somewhat similar to
+`threading.BoundedSemaphore`:
+
+>>> import portalocker
+>>> n = 2
+>>> timeout = 0.1
+
+>>> semaphore_a = portalocker.BoundedSemaphore(n, timeout=timeout)
+>>> semaphore_b = portalocker.BoundedSemaphore(n, timeout=timeout)
+>>> semaphore_c = portalocker.BoundedSemaphore(n, timeout=timeout)
+
+>>> semaphore_a.acquire()
+<portalocker.utils.Lock object at ...>
+>>> semaphore_b.acquire()
+<portalocker.utils.Lock object at ...>
+>>> semaphore_c.acquire()
+Traceback (most recent call last):
+  ...
+portalocker.exceptions.AlreadyLocked
+
+
+More examples can be found in the
+`tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
+
+
+Versioning
+----------
+
+This library follows `Semantic Versioning <http://semver.org/>`_.
+
+
+Changelog
+---------
+
+Every release has a ``git tag`` with a commit message for the tag
+explaining what was added and/or changed. The list of tags/releases
+including the commit messages can be found here:
+https://github.com/WoLpH/portalocker/releases
+
+License
+-------
+
+See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
+
diff --git a/portalocker.egg-info/SOURCES.txt b/portalocker.egg-info/SOURCES.txt
index db40da5..380671d 100644
--- a/portalocker.egg-info/SOURCES.txt
+++ b/portalocker.egg-info/SOURCES.txt
@@ -9,6 +9,7 @@ portalocker/__init__.py
 portalocker/constants.py
 portalocker/exceptions.py
 portalocker/portalocker.py
+portalocker/py.typed
 portalocker/redis.py
 portalocker/utils.py
 portalocker.egg-info/PKG-INFO
diff --git a/portalocker.egg-info/requires.txt b/portalocker.egg-info/requires.txt
index eb596f3..308bef4 100644
--- a/portalocker.egg-info/requires.txt
+++ b/portalocker.egg-info/requires.txt
@@ -1,6 +1,6 @@
 
 [:platform_system == "Windows"]
-pywin32!=226
+pywin32>=226
 
 [docs]
 sphinx>=1.7.1
@@ -11,7 +11,7 @@ redis
 [tests]
 pytest>=5.4.1
 pytest-cov>=2.8.1
+pytest-timeout>=2.1.0
 sphinx>=3.0.3
-pytest-flake8>=1.0.5
 pytest-mypy>=0.8.0
 redis
diff --git a/portalocker/__about__.py b/portalocker/__about__.py
index 3bed460..2264351 100644
--- a/portalocker/__about__.py
+++ b/portalocker/__about__.py
@@ -1,7 +1,7 @@
 __package_name__ = 'portalocker'
 __author__ = 'Rick van Hattem'
 __email__ = 'wolph@wol.ph'
-__version__ = '2.2.1'
+__version__ = '2.6.0'
 __description__ = '''Wraps the portalocker recipe for easy usage'''
 __url__ = 'https://github.com/WoLpH/portalocker'
 
diff --git a/portalocker/__init__.py b/portalocker/__init__.py
index b20b3d7..65ddb4a 100644
--- a/portalocker/__init__.py
+++ b/portalocker/__init__.py
@@ -17,7 +17,7 @@ __author__ = __about__.__author__
 #: Current author's email address
 __email__ = __about__.__email__
 #: Version number
-__version__ = '2.2.1'
+__version__ = '2.6.0'
 #: Package description for Pypi
 __description__ = __about__.__description__
 #: Package homepage
diff --git a/portalocker/exceptions.py b/portalocker/exceptions.py
index 0a815b9..b360c77 100644
--- a/portalocker/exceptions.py
+++ b/portalocker/exceptions.py
@@ -1,19 +1,27 @@
+import typing
+
+
 class BaseLockException(Exception):
     # Error codes:
     LOCK_FAILED = 1
 
-    def __init__(self, *args, fh=None, **kwargs):
+    def __init__(
+        self,
+        *args: typing.Any,
+        fh: typing.Optional[typing.IO] = None,
+        **kwargs: typing.Any,
+    ) -> None:
         self.fh = fh
-        Exception.__init__(self, *args, **kwargs)
+        Exception.__init__(self, *args)
 
 
 class LockException(BaseLockException):
     pass
 
 
-class AlreadyLocked(BaseLockException):
+class AlreadyLocked(LockException):
     pass
 
 
-class FileToLarge(BaseLockException):
+class FileToLarge(LockException):
     pass
diff --git a/portalocker/portalocker.py b/portalocker/portalocker.py
index 6bc7447..72260a7 100644
--- a/portalocker/portalocker.py
+++ b/portalocker/portalocker.py
@@ -1,87 +1,60 @@
 import os
-import sys
 
 import typing
 
 from . import constants
 from . import exceptions
 
+
+# Alias for readability. Due to import recursion issues we cannot do:
+# from .constants import LockFlags
+LockFlags = constants.LockFlags
+
+
 if os.name == 'nt':  # pragma: no cover
+    import msvcrt
+    import pywintypes
     import win32con
     import win32file
-    import pywintypes
     import winerror
-    import msvcrt
+
     __overlapped = pywintypes.OVERLAPPED()
 
-    if sys.version_info.major == 2:
-        lock_length = -1
-    else:
-        lock_length = int(2**31 - 1)
 
-    def lock(file_: typing.IO, flags: constants.LockFlags):
-        if flags & constants.LockFlags.SHARED:
-            if sys.version_info.major == 2:
-                if flags & constants.LockFlags.NON_BLOCKING:
-                    mode = win32con.LOCKFILE_FAIL_IMMEDIATELY
-                else:
-                    mode = 0
+    def lock(file_: typing.IO, flags: LockFlags):
+        mode = 0
+        if flags & LockFlags.NON_BLOCKING:
+            mode |= win32con.LOCKFILE_FAIL_IMMEDIATELY
 
-            else:
-                if flags & constants.LockFlags.NON_BLOCKING:
-                    mode = msvcrt.LK_NBRLCK
-                else:
-                    mode = msvcrt.LK_RLCK
+        if flags & LockFlags.EXCLUSIVE:
+            mode |= win32con.LOCKFILE_EXCLUSIVE_LOCK
 
-            # is there any reason not to reuse the following structure?
-            hfile = win32file._get_osfhandle(file_.fileno())
-            try:
-                win32file.LockFileEx(hfile, mode, 0, -0x10000, __overlapped)
-            except pywintypes.error as exc_value:
-                # error: (33, 'LockFileEx', 'The process cannot access the file
-                # because another process has locked a portion of the file.')
-                if exc_value.winerror == winerror.ERROR_LOCK_VIOLATION:
-                    raise exceptions.LockException(
-                        exceptions.LockException.LOCK_FAILED,
-                        exc_value.strerror,
-                        fh=file_)
-                else:
-                    # Q:  Are there exceptions/codes we should be dealing with
-                    # here?
-                    raise
-        else:
-            if flags & constants.LockFlags.NON_BLOCKING:
-                mode = msvcrt.LK_NBLCK
+        # Save the old position so we can go back to that position but
+        # still lock from the beginning of the file
+        savepos = file_.tell()
+        if savepos:
+            file_.seek(0)
+
+        os_fh = msvcrt.get_osfhandle(file_.fileno())
+        try:
+            win32file.LockFileEx(os_fh, mode, 0, -0x10000, __overlapped)
+        except pywintypes.error as exc_value:
+            # error: (33, 'LockFileEx', 'The process cannot access the file
+            # because another process has locked a portion of the file.')
+            if exc_value.winerror == winerror.ERROR_LOCK_VIOLATION:
+                raise exceptions.AlreadyLocked(
+                    exceptions.LockException.LOCK_FAILED,
+                    exc_value.strerror,
+                    fh=file_
+                )
             else:
-                mode = msvcrt.LK_LOCK
+                # Q:  Are there exceptions/codes we should be dealing with
+                # here?
+                raise
+        finally:
+            if savepos:
+                file_.seek(savepos)
 
-            # windows locks byte ranges, so make sure to lock from file start
-            try:
-                savepos = file_.tell()
-                if savepos:
-                    # [ ] test exclusive lock fails on seek here
-                    # [ ] test if shared lock passes this point
-                    file_.seek(0)
-                    # [x] check if 0 param locks entire file (not documented in
-                    #     Python)
-                    # [x] fails with "IOError: [Errno 13] Permission denied",
-                    #     but -1 seems to do the trick
-
-                try:
-                    msvcrt.locking(file_.fileno(), mode, lock_length)
-                except IOError as exc_value:
-                    # [ ] be more specific here
-                    raise exceptions.LockException(
-                        exceptions.LockException.LOCK_FAILED,
-                        exc_value.strerror,
-                        fh=file_)
-                finally:
-                    if savepos:
-                        file_.seek(savepos)
-            except IOError as exc_value:
-                raise exceptions.LockException(
-                    exceptions.LockException.LOCK_FAILED, exc_value.strerror,
-                    fh=file_)
 
     def unlock(file_: typing.IO):
         try:
@@ -89,61 +62,59 @@ if os.name == 'nt':  # pragma: no cover
             if savepos:
                 file_.seek(0)
 
+            os_fh = msvcrt.get_osfhandle(file_.fileno())
             try:
-                msvcrt.locking(file_.fileno(), constants.LockFlags.UNBLOCK,
-                               lock_length)
-            except IOError as exc:
-                exception = exc
-                if exc.strerror == 'Permission denied':
-                    hfile = win32file._get_osfhandle(file_.fileno())
-                    try:
-                        win32file.UnlockFileEx(
-                            hfile, 0, -0x10000, __overlapped)
-                    except pywintypes.error as exc:
-                        exception = exc
-                        if exc.winerror == winerror.ERROR_NOT_LOCKED:
-                            # error: (158, 'UnlockFileEx',
-                            #         'The segment is already unlocked.')
-                            # To match the 'posix' implementation, silently
-                            # ignore this error
-                            pass
-                        else:
-                            # Q:  Are there exceptions/codes we should be
-                            # dealing with here?
-                            raise
+                win32file.UnlockFileEx(
+                    os_fh, 0, -0x10000, __overlapped
+                )
+            except pywintypes.error as exc:
+                if exc.winerror == winerror.ERROR_NOT_LOCKED:
+                    # error: (158, 'UnlockFileEx',
+                    #         'The segment is already unlocked.')
+                    # To match the 'posix' implementation, silently
+                    # ignore this error
+                    pass
                 else:
-                    raise exceptions.LockException(
-                        exceptions.LockException.LOCK_FAILED,
-                        exception.strerror,
-                        fh=file_)
+                    # Q:  Are there exceptions/codes we should be
+                    # dealing with here?
+                    raise
             finally:
                 if savepos:
                     file_.seek(savepos)
         except IOError as exc:
             raise exceptions.LockException(
                 exceptions.LockException.LOCK_FAILED, exc.strerror,
-                fh=file_)
+                fh=file_
+            )
 
 elif os.name == 'posix':  # pragma: no cover
     import fcntl
 
-    def lock(file_: typing.IO, flags: constants.LockFlags):
+
+    def lock(file_: typing.IO, flags: LockFlags):
         locking_exceptions = IOError,
         try:  # pragma: no cover
             locking_exceptions += BlockingIOError,  # type: ignore
         except NameError:  # pragma: no cover
             pass
 
+        # 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)):
+            raise RuntimeError('When locking in non-blocking mode the SHARED '
+                               'or EXCLUSIVE flag must be specified as well')
+
         try:
-            fcntl.flock(file_.fileno(), flags)
+            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_)
 
+
     def unlock(file_: typing.IO, ):
-        fcntl.flock(file_.fileno(), constants.LockFlags.UNBLOCK)
+        fcntl.flock(file_.fileno(), LockFlags.UNBLOCK)
 
 else:  # pragma: no cover
     raise RuntimeError('PortaLocker only defined for nt and posix platforms')
-
diff --git a/portalocker/py.typed b/portalocker/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/portalocker/redis.py b/portalocker/redis.py
index b2468b9..08dbd4a 100644
--- a/portalocker/redis.py
+++ b/portalocker/redis.py
@@ -18,7 +18,7 @@ DEFAULT_UNAVAILABLE_TIMEOUT = 1
 DEFAULT_THREAD_SLEEP_TIME = 0.1
 
 
-class PubSubWorkerThread(client.PubSubWorkerThread):
+class PubSubWorkerThread(client.PubSubWorkerThread):  # type: ignore
 
     def run(self):
         try:
@@ -152,7 +152,7 @@ class RedisLock(utils.LockBase):
                         self.unavailable_timeout):  # pragma: no branch
                     continue
                 else:  # pragma: no cover
-                    subscribers = None
+                    subscribers = 0
 
             # Note: this should not be changed to an elif because the if
             # above can still end up here
diff --git a/portalocker/utils.py b/portalocker/utils.py
index 4226e9e..d7c94ca 100644
--- a/portalocker/utils.py
+++ b/portalocker/utils.py
@@ -1,17 +1,21 @@
 import abc
 import atexit
 import contextlib
+import logging
 import os
 import pathlib
 import random
 import tempfile
 import time
 import typing
+import warnings
 
 from . import constants
 from . import exceptions
 from . import portalocker
 
+logger = logging.getLogger(__name__)
+
 DEFAULT_TIMEOUT = 5
 DEFAULT_CHECK_INTERVAL = 0.25
 DEFAULT_FAIL_WHEN_LOCKED = False
@@ -25,7 +29,7 @@ __all__ = [
 Filename = typing.Union[str, pathlib.Path]
 
 
-def coalesce(*args, test_value=None):
+def coalesce(*args: typing.Any, test_value: typing.Any = None) -> typing.Any:
     '''Simple coalescing function that returns the first value that is not
     equal to the `test_value`. Or `None` if no value is valid. Usually this
     means that the last given value is the default value.
@@ -53,7 +57,8 @@ def coalesce(*args, test_value=None):
 
 
 @contextlib.contextmanager
-def open_atomic(filename: Filename, binary: bool = True):
+def open_atomic(filename: Filename, binary: bool = True) \
+        -> typing.Iterator[typing.IO]:
     '''Open a file for atomic writing. Instead of locking this method allows
     you to write the entire file and move it to the actual location. Note that
     this makes the assumption that a rename is atomic on your platform which
@@ -126,21 +131,23 @@ class LockBase(abc.ABC):  # pragma: no cover
             fail_when_locked: bool = None):
         return NotImplemented
 
-    def _timeout_generator(self, timeout, check_interval):
-        timeout = coalesce(timeout, self.timeout, 0.0)
-        check_interval = coalesce(check_interval, self.check_interval, 0.0)
+    def _timeout_generator(self, timeout: typing.Optional[float],
+                           check_interval: typing.Optional[float]) \
+            -> typing.Iterator[int]:
+        f_timeout = coalesce(timeout, self.timeout, 0.0)
+        f_check_interval = coalesce(check_interval, self.check_interval, 0.0)
 
         yield 0
         i = 0
 
         start_time = time.perf_counter()
-        while start_time + timeout > time.perf_counter():
+        while start_time + f_timeout > time.perf_counter():
             i += 1
             yield i
 
             # Take low lock checks into account to stay within the interval
             since_start_time = time.perf_counter() - start_time
-            time.sleep(max(0.001, (i * check_interval) - since_start_time))
+            time.sleep(max(0.001, (i * f_check_interval) - since_start_time))
 
     @abc.abstractmethod
     def release(self):
@@ -149,15 +156,20 @@ class LockBase(abc.ABC):  # pragma: no cover
     def __enter__(self):
         return self.acquire()
 
-    def __exit__(self, type_, value, tb):
+    def __exit__(self,
+                 exc_type: typing.Optional[typing.Type[BaseException]],
+                 exc_value: typing.Optional[BaseException],
+                 traceback: typing.Any,  # Should be typing.TracebackType
+                 ) -> typing.Optional[bool]:
         self.release()
+        return None
 
     def __delete__(self, instance):
         instance.release()
 
 
 class Lock(LockBase):
-    '''Lock manager with build-in timeout
+    '''Lock manager with built-in timeout
 
     Args:
         filename: filename
@@ -182,7 +194,7 @@ class Lock(LockBase):
             self,
             filename: Filename,
             mode: str = 'a',
-            timeout: float = DEFAULT_TIMEOUT,
+            timeout: float = None,
             check_interval: float = DEFAULT_CHECK_INTERVAL,
             fail_when_locked: bool = DEFAULT_FAIL_WHEN_LOCKED,
             flags: constants.LockFlags = LOCK_METHOD, **file_open_kwargs):
@@ -192,6 +204,11 @@ class Lock(LockBase):
         else:
             truncate = False
 
+        if timeout is None:
+            timeout = DEFAULT_TIMEOUT
+        elif not (flags & constants.LockFlags.NON_BLOCKING):
+            warnings.warn('timeout has no effect in blocking mode')
+
         self.fh: typing.Optional[typing.IO] = None
         self.filename: str = str(filename)
         self.mode: str = mode
@@ -209,6 +226,10 @@ class Lock(LockBase):
 
         fail_when_locked = coalesce(fail_when_locked, self.fail_when_locked)
 
+        if not (self.flags & constants.LockFlags.NON_BLOCKING) \
+                and timeout is not None:
+            warnings.warn('timeout has no effect in blocking mode')
+
         # If we already have a filehandle, return it
         fh = self.fh
         if fh:
@@ -397,10 +418,9 @@ class BoundedSemaphore(LockBase):
         assert not self.lock, 'Already locked'
 
         filenames = self.get_filenames()
-        print('filenames', filenames)
 
-        for _ in self._timeout_generator(timeout, check_interval):  # pragma:
-            print('trying lock', filenames)
+        for n in self._timeout_generator(timeout, check_interval):  # pragma:
+            logger.debug('trying lock (attempt %d) %r', n, filenames)
             # no branch
             if self.try_lock(filenames):  # pragma: no branch
                 return self.lock  # pragma: no cover
@@ -410,11 +430,11 @@ class BoundedSemaphore(LockBase):
     def try_lock(self, filenames: typing.Sequence[Filename]) -> bool:
         filename: Filename
         for filename in filenames:
-            print('trying lock for', filename)
+            logger.debug('trying lock for %r', filename)
             self.lock = Lock(filename, fail_when_locked=True)
             try:
                 self.lock.acquire()
-                print('locked', filename)
+                logger.debug('locked %r', filename)
                 return True
             except exceptions.AlreadyLocked:
                 pass
diff --git a/portalocker_tests/conftest.py b/portalocker_tests/conftest.py
index f751d38..cf59e2b 100644
--- a/portalocker_tests/conftest.py
+++ b/portalocker_tests/conftest.py
@@ -1,17 +1,23 @@
-import py
 import logging
 import pytest
-
+import random
+import multiprocessing
 
 logger = logging.getLogger(__name__)
 
 
 @pytest.fixture
-def tmpfile(tmpdir_factory):
-    tmpdir = tmpdir_factory.mktemp('temp')
-    filename = tmpdir.join('tmpfile')
+def tmpfile(tmp_path):
+    filename = tmp_path / str(random.random())
     yield str(filename)
     try:
-        filename.remove(ignore_errors=True)
-    except (py.error.EBUSY, py.error.ENOENT):
+        filename.unlink(missing_ok=True)
+    except PermissionError:
         pass
+
+
+def pytest_sessionstart(session):
+    # Force spawning the process so we don't accidently inherit locks.
+    # I'm not a 100% certain this will work correctly unfortunately... there
+    # is some potential for breaking tests
+    multiprocessing.set_start_method('spawn')
diff --git a/portalocker_tests/tests.py b/portalocker_tests/tests.py
index 3cc9758..7a00405 100644
--- a/portalocker_tests/tests.py
+++ b/portalocker_tests/tests.py
@@ -1,9 +1,16 @@
 from __future__ import print_function
 from __future__ import with_statement
 
+import os
+import dataclasses
+import multiprocessing
+import time
+import typing
+
 import pytest
 import portalocker
 from portalocker import utils
+from portalocker import LockFlags
 
 
 def test_exceptions(tmpfile):
@@ -36,8 +43,10 @@ def test_with_timeout(tmpfile):
     with pytest.raises(portalocker.AlreadyLocked):
         with portalocker.Lock(tmpfile, timeout=0.1) as fh:
             print('writing some stuff to my cache...', file=fh)
-            with portalocker.Lock(tmpfile, timeout=0.1, mode='wb',
-                                  fail_when_locked=True):
+            with portalocker.Lock(
+                tmpfile, timeout=0.1, mode='wb',
+                fail_when_locked=True
+            ):
                 pass
             print('writing more stuff to my cache...', file=fh)
 
@@ -206,3 +215,143 @@ def test_shared(tmpfile):
     portalocker.unlock(f)
     f.close()
 
+
+def test_blocking_timeout(tmpfile):
+    flags = LockFlags.SHARED
+
+    with pytest.warns(UserWarning):
+        with portalocker.Lock(tmpfile, timeout=5, flags=flags):
+            pass
+
+    lock = portalocker.Lock(tmpfile, flags=flags)
+    with pytest.warns(UserWarning):
+        lock.acquire(timeout=5)
+
+
+@pytest.mark.skipif(os.name == 'nt',
+                    reason='Windows uses an entirely different lockmechanism')
+def test_nonblocking(tmpfile):
+    with open(tmpfile, 'w') as fh:
+        with pytest.raises(RuntimeError):
+            portalocker.lock(fh, LockFlags.NON_BLOCKING)
+
+
+def shared_lock(filename, **kwargs):
+    with portalocker.Lock(
+        filename,
+        timeout=0.1,
+        fail_when_locked=False,
+        flags=LockFlags.SHARED | LockFlags.NON_BLOCKING,
+    ):
+        time.sleep(0.2)
+        return True
+
+
+def shared_lock_fail(filename, **kwargs):
+    with portalocker.Lock(
+        filename,
+        timeout=0.1,
+        fail_when_locked=True,
+        flags=LockFlags.SHARED | LockFlags.NON_BLOCKING,
+    ):
+        time.sleep(0.2)
+        return True
+
+
+def exclusive_lock(filename, **kwargs):
+    with portalocker.Lock(
+        filename,
+        timeout=0.1,
+        fail_when_locked=False,
+        flags=LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING,
+    ):
+        time.sleep(0.2)
+        return True
+
+
+@dataclasses.dataclass(order=True)
+class LockResult:
+    exception_class: typing.Union[typing.Type[Exception], None] = None
+    exception_message: typing.Union[str, None] = None
+    exception_repr: typing.Union[str, None] = None
+
+
+def lock(
+    filename: str,
+    fail_when_locked: bool,
+    flags: LockFlags
+) -> LockResult:
+    # Returns a case of True, False or FileNotFound
+    # https://thedailywtf.com/articles/what_is_truth_0x3f_
+    # But seriously, the exception properties cannot be safely pickled so we
+    # only return string representations of the exception properties
+    try:
+        with portalocker.Lock(
+            filename,
+            timeout=0.1,
+            fail_when_locked=fail_when_locked,
+            flags=flags,
+        ):
+            time.sleep(0.2)
+            return LockResult()
+
+    except Exception as exception:
+        # The exceptions cannot be pickled so we cannot return them through
+        # multiprocessing
+        return LockResult(
+            type(exception),
+            str(exception),
+            repr(exception),
+        )
+
+
+@pytest.mark.parametrize('fail_when_locked', [True, False])
+def test_shared_processes(tmpfile, fail_when_locked):
+    flags = LockFlags.SHARED | LockFlags.NON_BLOCKING
+
+    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):
+            assert result == LockResult()
+
+
+@pytest.mark.parametrize('fail_when_locked', [True, False])
+def test_exclusive_processes(tmpfile, fail_when_locked):
+    flags = LockFlags.EXCLUSIVE | LockFlags.NON_BLOCKING
+
+    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,
+            portalocker.LockException
+        )
+
+
+@pytest.mark.skipif(
+    os.name == 'nt',
+    reason='Locking on Windows requires a file object',
+)
+def test_lock_fileno(tmpfile):
+    # Open the file 2 times
+    a = open(tmpfile, 'a')
+    b = open(tmpfile, 'a')
+
+    # Lock exclusive non-blocking
+    flags = LockFlags.SHARED | LockFlags.NON_BLOCKING
+
+    # First lock file a
+    portalocker.lock(a, flags)
+
+    # Now see if we can lock using fileno()
+    portalocker.lock(b.fileno(), flags)
+
+    # Cleanup
+    a.close()
+    b.close()
+
diff --git a/setup.cfg b/setup.cfg
index 6281d19..e730222 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,17 +1,14 @@
 [metadata]
-description-file = README.rst
-
-[build_sphinx]
-source-dir = docs/
-build-dir = docs/_build
-all_files = 1
-
-[upload_sphinx]
-upload-dir = docs/_build/html
+description_file = README.rst
 
 [bdist_wheel]
 universal = 1
 
+[flake8]
+ignore = 
+	*.py W391,E303,W503
+	docs/*.py ALL
+
 [egg_info]
 tag_build = 
 tag_date = 0
diff --git a/setup.py b/setup.py
index 4365703..bd15ad6 100644
--- a/setup.py
+++ b/setup.py
@@ -2,20 +2,9 @@ from __future__ import print_function
 
 import os
 import re
-import sys
 import typing
-from distutils.version import LooseVersion
 
 import setuptools
-from setuptools import __version__ as setuptools_version
-from setuptools.command.test import test as TestCommand
-
-if LooseVersion(setuptools_version) < LooseVersion('38.3.0'):
-    raise SystemExit(
-        'Your `setuptools` version is old. '
-        'Please upgrade setuptools by running `pip install -U setuptools` '
-        'and try again.'
-    )
 
 # To prevent importing about and thereby breaking the coverage info we use this
 # exec hack
@@ -26,28 +15,13 @@ with open('portalocker/__about__.py') as fp:
 tests_require = [
     'pytest>=5.4.1',
     'pytest-cov>=2.8.1',
+    'pytest-timeout>=2.1.0',
     'sphinx>=3.0.3',
-    'pytest-flake8>=1.0.5',
     'pytest-mypy>=0.8.0',
     'redis',
 ]
 
 
-class PyTest(TestCommand):
-    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
-
-    def initialize_options(self):
-        TestCommand.initialize_options(self)
-        self.pytest_args = ''
-
-    def run_tests(self):
-        import shlex
-        # import here, cause outside the eggs aren't loaded
-        import pytest
-        errno = pytest.main(shlex.split(self.pytest_args))
-        sys.exit(errno)
-
-
 class Combine(setuptools.Command):
     description = 'Build single combined portalocker file'
     relative_import_re = re.compile(r'^from \. import (?P<name>.+)$',
@@ -113,29 +87,32 @@ if __name__ == '__main__':
         classifiers=[
             'Intended Audience :: Developers',
             'Programming Language :: Python',
-            'Programming Language :: Python :: 2.7',
-            'Programming Language :: Python :: 3.3',
-            'Programming Language :: Python :: 3.4',
             'Programming Language :: Python :: 3.5',
             'Programming Language :: Python :: 3.6',
+            'Programming Language :: Python :: 3.7',
+            'Programming Language :: Python :: 3.8',
+            'Programming Language :: Python :: 3.9',
+            'Programming Language :: Python :: 3.10',
             'Programming Language :: Python :: Implementation :: CPython',
             'Programming Language :: Python :: Implementation :: PyPy',
         ],
+        python_requires='>=3.5',
         keywords='locking, locks, with statement, windows, linux, unix',
         author=about['__author__'],
         author_email=about['__email__'],
         url=about['__url__'],
         license='PSF',
+        package_data=dict(portalocker=['py.typed', 'msvcrt.pyi']),
         packages=setuptools.find_packages(exclude=[
             'examples', 'portalocker_tests']),
         # zip_safe=False,
         platforms=['any'],
         cmdclass={
             'combine': Combine,
-            'test': PyTest,
         },
         install_requires=[
-            'pywin32!=226; platform_system == "Windows"',
+            # Due to CVE-2021-32559 updating the pywin32 requirement
+            'pywin32>=226; platform_system == "Windows"',
         ],
         tests_require=tests_require,
         extras_require=dict(

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.6.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.6.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.6.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.6.0.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker/py.typed

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.2.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.2.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.2.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/portalocker-2.2.1.egg-info/top_level.txt

No differences were encountered in the control files

More details

Full run details