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