summaryrefslogtreecommitdiffstats
path: root/contrib/python/portalocker/py2
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-08-28 14:27:58 +0300
committerrobot-piglet <[email protected]>2025-08-28 14:57:06 +0300
commit81d828c32c8d5477cb2f0ce5da06a1a8d9392ca3 (patch)
tree3081d566f0d5158d76e9093261344f6406fd09f7 /contrib/python/portalocker/py2
parent77ea11423f959e51795cc3ef36a48d808b4ffb98 (diff)
Intermediate changes
commit_hash:d5b1af16dbe9030537a04c27eb410c88c2f496cd
Diffstat (limited to 'contrib/python/portalocker/py2')
-rw-r--r--contrib/python/portalocker/py2/.dist-info/METADATA136
-rw-r--r--contrib/python/portalocker/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/portalocker/py2/.yandex_meta/yamaker.yaml4
-rw-r--r--contrib/python/portalocker/py2/LICENSE48
-rw-r--r--contrib/python/portalocker/py2/README.rst106
-rw-r--r--contrib/python/portalocker/py2/portalocker/__about__.py7
-rw-r--r--contrib/python/portalocker/py2/portalocker/__init__.py67
-rw-r--r--contrib/python/portalocker/py2/portalocker/constants.py39
-rw-r--r--contrib/python/portalocker/py2/portalocker/exceptions.py19
-rw-r--r--contrib/python/portalocker/py2/portalocker/portalocker.py148
-rw-r--r--contrib/python/portalocker/py2/portalocker/utils.py256
-rw-r--r--contrib/python/portalocker/py2/tests/__init__.py0
-rw-r--r--contrib/python/portalocker/py2/tests/conftest.py14
-rw-r--r--contrib/python/portalocker/py2/tests/temporary_file_lock.py14
-rw-r--r--contrib/python/portalocker/py2/tests/test_combined.py15
-rw-r--r--contrib/python/portalocker/py2/tests/tests.py202
-rw-r--r--contrib/python/portalocker/py2/tests/ya.make20
-rw-r--r--contrib/python/portalocker/py2/ya.make33
18 files changed, 1129 insertions, 0 deletions
diff --git a/contrib/python/portalocker/py2/.dist-info/METADATA b/contrib/python/portalocker/py2/.dist-info/METADATA
new file mode 100644
index 00000000000..d01a6203e0a
--- /dev/null
+++ b/contrib/python/portalocker/py2/.dist-info/METADATA
@@ -0,0 +1,136 @@
+Metadata-Version: 2.1
+Name: portalocker
+Version: 1.7.1
+Summary: Wraps the portalocker recipe for easy usage
+Home-page: https://github.com/WoLpH/portalocker
+Author: Rick van Hattem
+Author-email: [email protected]
+License: PSF
+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 :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Requires-Dist: pywin32 (!=226) ; platform_system == "Windows"
+Provides-Extra: docs
+Requires-Dist: sphinx (>=1.7.1) ; extra == 'docs'
+Provides-Extra: tests
+Requires-Dist: pytest (>=4.6.9) ; extra == 'tests'
+Requires-Dist: pytest-cov (>=2.8.1) ; extra == 'tests'
+Requires-Dist: sphinx (>=1.8.5) ; extra == 'tests'
+Requires-Dist: pytest-flake8 (>=1.0.5) ; extra == 'tests'
+
+############################################
+portalocker - Cross-platform locking library
+############################################
+
+.. image:: https://travis-ci.org/WoLpH/portalocker.svg?branch=master
+ :alt: Linux Test Status
+ :target: https://travis-ci.org/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 <[email protected]>.
+The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
+requests can be submitted there. Patches are also very welcome.
+
+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.LOCK_EX)
+>>> file.seek(12)
+>>> file.write('foo')
+>>> file.close()
+
+Explicitly unlocking might not be needed in all cases:
+https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
+
+But 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()`.
+
+More examples can be found in the
+`tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
+
+Changelog
+---------
+
+See the `changelog <http://portalocker.readthedocs.io/en/latest/changelog.html>`_ page.
+
+License
+-------
+
+See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
+
+
+
diff --git a/contrib/python/portalocker/py2/.dist-info/top_level.txt b/contrib/python/portalocker/py2/.dist-info/top_level.txt
new file mode 100644
index 00000000000..7bbc14e6fa6
--- /dev/null
+++ b/contrib/python/portalocker/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+portalocker
diff --git a/contrib/python/portalocker/py2/.yandex_meta/yamaker.yaml b/contrib/python/portalocker/py2/.yandex_meta/yamaker.yaml
new file mode 100644
index 00000000000..bca0c4e5337
--- /dev/null
+++ b/contrib/python/portalocker/py2/.yandex_meta/yamaker.yaml
@@ -0,0 +1,4 @@
+keep:
+ - tests/*
+exclude:
+ - portalocker_tests/*
diff --git a/contrib/python/portalocker/py2/LICENSE b/contrib/python/portalocker/py2/LICENSE
new file mode 100644
index 00000000000..adb8038169c
--- /dev/null
+++ b/contrib/python/portalocker/py2/LICENSE
@@ -0,0 +1,48 @@
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+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.
+
+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.
+
+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.
+
+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.
+
diff --git a/contrib/python/portalocker/py2/README.rst b/contrib/python/portalocker/py2/README.rst
new file mode 100644
index 00000000000..c013490c765
--- /dev/null
+++ b/contrib/python/portalocker/py2/README.rst
@@ -0,0 +1,106 @@
+############################################
+portalocker - Cross-platform locking library
+############################################
+
+.. image:: https://travis-ci.org/WoLpH/portalocker.svg?branch=master
+ :alt: Linux Test Status
+ :target: https://travis-ci.org/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 <[email protected]>.
+The project resides at https://github.com/WoLpH/portalocker . Bugs and feature
+requests can be submitted there. Patches are also very welcome.
+
+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.LOCK_EX)
+>>> file.seek(12)
+>>> file.write('foo')
+>>> file.close()
+
+Explicitly unlocking might not be needed in all cases:
+https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/42#issuecomment-601108266
+
+But 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()`.
+
+More examples can be found in the
+`tests <http://portalocker.readthedocs.io/en/latest/_modules/tests/tests.html>`_.
+
+Changelog
+---------
+
+See the `changelog <http://portalocker.readthedocs.io/en/latest/changelog.html>`_ page.
+
+License
+-------
+
+See the `LICENSE <https://github.com/WoLpH/portalocker/blob/develop/LICENSE>`_ file.
+
diff --git a/contrib/python/portalocker/py2/portalocker/__about__.py b/contrib/python/portalocker/py2/portalocker/__about__.py
new file mode 100644
index 00000000000..f16fe0cdf7d
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/__about__.py
@@ -0,0 +1,7 @@
+__package_name__ = 'portalocker'
+__author__ = 'Rick van Hattem'
+__email__ = '[email protected]'
+__version__ = '1.7.1'
+__description__ = '''Wraps the portalocker recipe for easy usage'''
+__url__ = 'https://github.com/WoLpH/portalocker'
+
diff --git a/contrib/python/portalocker/py2/portalocker/__init__.py b/contrib/python/portalocker/py2/portalocker/__init__.py
new file mode 100644
index 00000000000..9bf27fee0fc
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/__init__.py
@@ -0,0 +1,67 @@
+from . import __about__
+from . import constants
+from . import exceptions
+from . import portalocker
+from . import utils
+
+#: The package name on Pypi
+__package_name__ = __about__.__package_name__
+#: Current author and maintainer, view the git history for the previous ones
+__author__ = __about__.__author__
+#: Current author's email address
+__email__ = __about__.__email__
+#: Version number
+__version__ = '1.7.1'
+#: Package description for Pypi
+__description__ = __about__.__description__
+#: Package homepage
+__url__ = __about__.__url__
+
+
+#: Exception thrown when the file is already locked by someone else
+AlreadyLocked = exceptions.AlreadyLocked
+#: Exception thrown if an error occurred during locking
+LockException = exceptions.LockException
+
+
+#: Lock a file. Note that this is an advisory lock on Linux/Unix systems
+lock = portalocker.lock
+#: Unlock a file
+unlock = portalocker.unlock
+
+#: Place an exclusive lock.
+#: Only one process may hold an exclusive lock for a given file at a given
+#: time.
+LOCK_EX = constants.LOCK_EX
+
+#: Place a shared lock.
+#: More than one process may hold a shared lock for a given file at a given
+#: time.
+LOCK_SH = constants.LOCK_SH
+
+#: Acquire the lock in a non-blocking fashion.
+LOCK_NB = constants.LOCK_NB
+
+#: Remove an existing lock held by this process.
+LOCK_UN = constants.LOCK_UN
+
+#: Locking utility class to automatically handle opening with timeouts and
+#: context wrappers
+Lock = utils.Lock
+RLock = utils.RLock
+TemporaryFileLock = utils.TemporaryFileLock
+open_atomic = utils.open_atomic
+
+__all__ = [
+ 'lock',
+ 'unlock',
+ 'LOCK_EX',
+ 'LOCK_SH',
+ 'LOCK_NB',
+ 'LOCK_UN',
+ 'LockException',
+ 'Lock',
+ 'AlreadyLocked',
+ 'open_atomic',
+]
+
diff --git a/contrib/python/portalocker/py2/portalocker/constants.py b/contrib/python/portalocker/py2/portalocker/constants.py
new file mode 100644
index 00000000000..fb0927e2da9
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/constants.py
@@ -0,0 +1,39 @@
+'''
+Locking constants
+
+Lock types:
+
+- `LOCK_EX` exclusive lock
+- `LOCK_SH` shared lock
+
+Lock flags:
+
+- `LOCK_NB` non-blocking
+
+Manually unlock, only needed internally
+
+- `LOCK_UN` unlock
+'''
+import os
+
+# The actual tests will execute the code anyhow so the following code can
+# safely be ignored from the coverage tests
+if os.name == 'nt': # pragma: no cover
+ import msvcrt
+
+ LOCK_EX = 0x1 #: exclusive lock
+ LOCK_SH = 0x2 #: shared lock
+ LOCK_NB = 0x4 #: non-blocking
+ LOCK_UN = msvcrt.LK_UNLCK #: unlock
+
+elif os.name == 'posix': # pragma: no cover
+ import fcntl
+
+ LOCK_EX = fcntl.LOCK_EX #: exclusive lock
+ LOCK_SH = fcntl.LOCK_SH #: shared lock
+ LOCK_NB = fcntl.LOCK_NB #: non-blocking
+ LOCK_UN = fcntl.LOCK_UN #: unlock
+
+else: # pragma: no cover
+ raise RuntimeError('PortaLocker only defined for nt and posix platforms')
+
diff --git a/contrib/python/portalocker/py2/portalocker/exceptions.py b/contrib/python/portalocker/py2/portalocker/exceptions.py
new file mode 100644
index 00000000000..bb2b35eb7bc
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/exceptions.py
@@ -0,0 +1,19 @@
+class BaseLockException(Exception):
+ # Error codes:
+ LOCK_FAILED = 1
+
+ def __init__(self, *args, **kwargs):
+ self.fh = kwargs.pop('fh', None)
+ Exception.__init__(self, *args, **kwargs)
+
+
+class LockException(BaseLockException):
+ pass
+
+
+class AlreadyLocked(BaseLockException):
+ pass
+
+
+class FileToLarge(BaseLockException):
+ pass
diff --git a/contrib/python/portalocker/py2/portalocker/portalocker.py b/contrib/python/portalocker/py2/portalocker/portalocker.py
new file mode 100644
index 00000000000..460cf06e476
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/portalocker.py
@@ -0,0 +1,148 @@
+import os
+import sys
+from . import exceptions
+from . import constants
+
+
+if os.name == 'nt': # pragma: no cover
+ 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_, flags):
+ if flags & constants.LOCK_SH:
+ if sys.version_info.major == 2:
+ if flags & constants.LOCK_NB:
+ mode = win32con.LOCKFILE_FAIL_IMMEDIATELY
+ else:
+ mode = 0
+
+ else:
+ if flags & constants.LOCK_NB:
+ mode = msvcrt.LK_NBRLCK
+ else:
+ mode = msvcrt.LK_RLCK
+
+ # 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:
+ mode = win32con.LOCKFILE_EXCLUSIVE_LOCK
+ if flags & constants.LOCK_NB:
+ mode |= win32con.LOCKFILE_FAIL_IMMEDIATELY
+
+ if flags & constants.LOCK_NB:
+ mode = msvcrt.LK_NBLCK
+ else:
+ mode = msvcrt.LK_LOCK
+
+ # 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_):
+ try:
+ savepos = file_.tell()
+ if savepos:
+ file_.seek(0)
+
+ try:
+ msvcrt.locking(file_.fileno(), constants.LOCK_UN, lock_length)
+ except IOError as exc_value:
+ if exc_value.strerror == 'Permission denied':
+ hfile = win32file._get_osfhandle(file_.fileno())
+ try:
+ win32file.UnlockFileEx(
+ hfile, 0, -0x10000, __overlapped)
+ except pywintypes.error as exc_value:
+ if exc_value.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
+ else:
+ 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_)
+
+elif os.name == 'posix': # pragma: no cover
+ import fcntl
+
+ def lock(file_, flags):
+ locking_exceptions = IOError,
+ try: # pragma: no cover
+ locking_exceptions += BlockingIOError,
+ except NameError: # pragma: no cover
+ pass
+
+ try:
+ fcntl.flock(file_.fileno(), 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_):
+ fcntl.flock(file_.fileno(), constants.LOCK_UN)
+
+else: # pragma: no cover
+ raise RuntimeError('PortaLocker only defined for nt and posix platforms')
+
diff --git a/contrib/python/portalocker/py2/portalocker/utils.py b/contrib/python/portalocker/py2/portalocker/utils.py
new file mode 100644
index 00000000000..8baebc2b200
--- /dev/null
+++ b/contrib/python/portalocker/py2/portalocker/utils.py
@@ -0,0 +1,256 @@
+import os
+import time
+import atexit
+import tempfile
+import contextlib
+from . import exceptions
+from . import constants
+from . import portalocker
+
+current_time = getattr(time, "monotonic", time.time)
+
+DEFAULT_TIMEOUT = 5
+DEFAULT_CHECK_INTERVAL = 0.25
+LOCK_METHOD = constants.LOCK_EX | constants.LOCK_NB
+
+__all__ = [
+ 'Lock',
+ 'open_atomic',
+]
+
+
+def open_atomic(filename, binary=True):
+ '''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
+ is generally the case but not a guarantee.
+
+ http://docs.python.org/library/os.html#os.rename
+
+ >>> filename = 'test_file.txt'
+ >>> if os.path.exists(filename):
+ ... os.remove(filename)
+
+ >>> with open_atomic(filename) as fh:
+ ... written = fh.write(b'test')
+ >>> assert os.path.exists(filename)
+ >>> os.remove(filename)
+
+ '''
+ assert not os.path.exists(filename), '%r exists' % filename
+ path, name = os.path.split(filename)
+
+ # Create the parent directory if it doesn't exist
+ if path and not os.path.isdir(path): # pragma: no cover
+ os.makedirs(path)
+
+ temp_fh = tempfile.NamedTemporaryFile(
+ mode=binary and 'wb' or 'w',
+ dir=path,
+ delete=False,
+ )
+ yield temp_fh
+ temp_fh.flush()
+ os.fsync(temp_fh.fileno())
+ temp_fh.close()
+ try:
+ os.rename(temp_fh.name, filename)
+ finally:
+ try:
+ os.remove(temp_fh.name)
+ except Exception:
+ pass
+
+
+class Lock(object):
+
+ def __init__(
+ self, filename, mode='a', timeout=DEFAULT_TIMEOUT,
+ check_interval=DEFAULT_CHECK_INTERVAL, fail_when_locked=False,
+ flags=LOCK_METHOD, **file_open_kwargs):
+ '''Lock manager with build-in timeout
+
+ filename -- filename
+ mode -- the open mode, 'a' or 'ab' should be used for writing
+ truncate -- use truncate to emulate 'w' mode, None is disabled, 0 is
+ truncate to 0 bytes
+ timeout -- timeout when trying to acquire a lock
+ check_interval -- check interval while waiting
+ fail_when_locked -- after the initial lock failed, return an error
+ or lock the file
+ **file_open_kwargs -- The kwargs for the `open(...)` call
+
+ fail_when_locked is useful when multiple threads/processes can race
+ when creating a file. If set to true than the system will wait till
+ the lock was acquired and then return an AlreadyLocked exception.
+
+ Note that the file is opened first and locked later. So using 'w' as
+ mode will result in truncate _BEFORE_ the lock is checked.
+ '''
+
+ if 'w' in mode:
+ truncate = True
+ mode = mode.replace('w', 'a')
+ else:
+ truncate = False
+
+ self.fh = None
+ self.filename = filename
+ self.mode = mode
+ self.truncate = truncate
+ self.timeout = timeout
+ self.check_interval = check_interval
+ self.fail_when_locked = fail_when_locked
+ self.flags = flags
+ self.file_open_kwargs = file_open_kwargs
+
+ def acquire(
+ self, timeout=None, check_interval=None, fail_when_locked=None):
+ '''Acquire the locked filehandle'''
+ if timeout is None:
+ timeout = self.timeout
+ if timeout is None:
+ timeout = 0
+
+ if check_interval is None:
+ check_interval = self.check_interval
+
+ if fail_when_locked is None:
+ fail_when_locked = self.fail_when_locked
+
+ # If we already have a filehandle, return it
+ fh = self.fh
+ if fh:
+ return fh
+
+ # Get a new filehandler
+ fh = self._get_fh()
+ try:
+ # Try to lock
+ fh = self._get_lock(fh)
+ except exceptions.LockException as exception:
+ # Try till the timeout has passed
+ timeoutend = current_time() + timeout
+ while timeoutend > current_time():
+ # Wait a bit
+ time.sleep(check_interval)
+
+ # Try again
+ try:
+
+ # We already tried to the get the lock
+ # If fail_when_locked is true, then stop trying
+ if fail_when_locked:
+ raise exceptions.AlreadyLocked(exception)
+
+ else: # pragma: no cover
+ # We've got the lock
+ fh = self._get_lock(fh)
+ break
+
+ except exceptions.LockException:
+ pass
+
+ else:
+ fh.close()
+ # We got a timeout... reraising
+ raise exceptions.LockException(exception)
+
+ # Prepare the filehandle (truncate if needed)
+ fh = self._prepare_fh(fh)
+
+ self.fh = fh
+ return fh
+
+ def release(self):
+ '''Releases the currently locked file handle'''
+ if self.fh:
+ portalocker.unlock(self.fh)
+ self.fh.close()
+ self.fh = None
+
+ def _get_fh(self):
+ '''Get a new filehandle'''
+ return open(self.filename, self.mode, **self.file_open_kwargs)
+
+ def _get_lock(self, fh):
+ '''
+ Try to lock the given filehandle
+
+ returns LockException if it fails'''
+ portalocker.lock(fh, self.flags)
+ return fh
+
+ def _prepare_fh(self, fh):
+ '''
+ Prepare the filehandle for usage
+
+ If truncate is a number, the file will be truncated to that amount of
+ bytes
+ '''
+ if self.truncate:
+ fh.seek(0)
+ fh.truncate(0)
+
+ return fh
+
+ def __enter__(self):
+ return self.acquire()
+
+ def __exit__(self, type_, value, tb):
+ self.release()
+
+ def __delete__(self, instance): # pragma: no cover
+ instance.release()
+
+
+class RLock(Lock):
+ """
+ A reentrant lock, functions in a similar way to threading.RLock in that it
+ can be acquired multiple times. When the corresponding number of release()
+ calls are made the lock will finally release the underlying file lock.
+ """
+ def __init__(
+ self, filename, mode='a', timeout=DEFAULT_TIMEOUT,
+ check_interval=DEFAULT_CHECK_INTERVAL, fail_when_locked=False,
+ flags=LOCK_METHOD):
+ super(RLock, self).__init__(filename, mode, timeout, check_interval,
+ fail_when_locked, flags)
+ self._acquire_count = 0
+
+ def acquire(
+ self, timeout=None, check_interval=None, fail_when_locked=None):
+ if self._acquire_count >= 1:
+ fh = self.fh
+ else:
+ fh = super(RLock, self).acquire(timeout, check_interval,
+ fail_when_locked)
+ self._acquire_count += 1
+ return fh
+
+ def release(self):
+ if self._acquire_count == 0:
+ raise exceptions.LockException(
+ "Cannot release more times than acquired")
+
+ if self._acquire_count == 1:
+ super(RLock, self).release()
+ self._acquire_count -= 1
+
+
+class TemporaryFileLock(Lock):
+
+ def __init__(self, filename='.lock', timeout=DEFAULT_TIMEOUT,
+ check_interval=DEFAULT_CHECK_INTERVAL, fail_when_locked=True,
+ flags=LOCK_METHOD):
+
+ Lock.__init__(self, filename=filename, mode='w', timeout=timeout,
+ check_interval=check_interval,
+ fail_when_locked=fail_when_locked, flags=flags)
+ atexit.register(self.release)
+
+ def release(self):
+ Lock.release(self)
+ if os.path.isfile(self.filename): # pragma: no branch
+ os.unlink(self.filename)
diff --git a/contrib/python/portalocker/py2/tests/__init__.py b/contrib/python/portalocker/py2/tests/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/__init__.py
diff --git a/contrib/python/portalocker/py2/tests/conftest.py b/contrib/python/portalocker/py2/tests/conftest.py
new file mode 100644
index 00000000000..a92117e3951
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/conftest.py
@@ -0,0 +1,14 @@
+import py
+import pytest
+
+
+def tmpfile(tmpdir_factory):
+ tmpdir = tmpdir_factory.mktemp('temp')
+ filename = tmpdir.join('tmpfile')
+ yield str(filename)
+ try:
+ filename.remove(ignore_errors=True)
+ except (py.error.EBUSY, py.error.ENOENT):
+ pass
+
diff --git a/contrib/python/portalocker/py2/tests/temporary_file_lock.py b/contrib/python/portalocker/py2/tests/temporary_file_lock.py
new file mode 100644
index 00000000000..b250bad6510
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/temporary_file_lock.py
@@ -0,0 +1,14 @@
+import os
+import portalocker
+
+
+def test_temporary_file_lock(tmpfile):
+ with portalocker.TemporaryFileLock(tmpfile):
+ pass
+
+ assert not os.path.isfile(tmpfile)
+
+ lock = portalocker.TemporaryFileLock(tmpfile)
+ lock.acquire()
+ del lock
+
diff --git a/contrib/python/portalocker/py2/tests/test_combined.py b/contrib/python/portalocker/py2/tests/test_combined.py
new file mode 100644
index 00000000000..594de74b9ba
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/test_combined.py
@@ -0,0 +1,15 @@
+import sys
+
+
+def test_combined(tmpdir):
+ from distutils import dist
+ import setup
+
+ output_file = tmpdir.join('combined.py')
+ combine = setup.Combine(dist.Distribution())
+ combine.output_file = str(output_file)
+ combine.run()
+ sys.path.append(output_file.dirname)
+ import combined
+ assert combined
+
diff --git a/contrib/python/portalocker/py2/tests/tests.py b/contrib/python/portalocker/py2/tests/tests.py
new file mode 100644
index 00000000000..a6567426be0
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/tests.py
@@ -0,0 +1,202 @@
+from __future__ import print_function
+from __future__ import with_statement
+
+import pytest
+import portalocker
+
+
+def test_exceptions(tmpfile):
+ # Open the file 2 times
+ a = open(tmpfile, 'a')
+ b = open(tmpfile, 'a')
+
+ # Lock exclusive non-blocking
+ lock_flags = portalocker.LOCK_EX | portalocker.LOCK_NB
+
+ # First lock file a
+ portalocker.lock(a, lock_flags)
+
+ # Now see if we can lock file b
+ with pytest.raises(portalocker.LockException):
+ portalocker.lock(b, lock_flags)
+
+ # Cleanup
+ a.close()
+ b.close()
+
+
+def test_with_timeout(tmpfile):
+ # Open the file 2 times
+ 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):
+ pass
+ print('writing more stuff to my cache...', file=fh)
+
+
+def test_without_timeout(tmpfile):
+ # Open the file 2 times
+ with pytest.raises(portalocker.LockException):
+ with portalocker.Lock(tmpfile, timeout=None) as fh:
+ print('writing some stuff to my cache...', file=fh)
+ with portalocker.Lock(tmpfile, timeout=None, mode='w'):
+ pass
+ print('writing more stuff to my cache...', file=fh)
+
+
+def test_without_fail(tmpfile):
+ # Open the file 2 times
+ with pytest.raises(portalocker.LockException):
+ with portalocker.Lock(tmpfile, timeout=0.1) as fh:
+ print('writing some stuff to my cache...', file=fh)
+ lock = portalocker.Lock(tmpfile, timeout=0.1)
+ lock.acquire(check_interval=0.05, fail_when_locked=False)
+
+
+def test_simple(tmpfile):
+ with open(tmpfile, 'w') as fh:
+ fh.write('spam and eggs')
+
+ fh = open(tmpfile, 'r+')
+ portalocker.lock(fh, portalocker.LOCK_EX)
+
+ fh.seek(13)
+ fh.write('foo')
+
+ # Make sure we didn't overwrite the original text
+ fh.seek(0)
+ assert fh.read(13) == 'spam and eggs'
+
+ portalocker.unlock(fh)
+ fh.close()
+
+
+def test_truncate(tmpfile):
+ with open(tmpfile, 'w') as fh:
+ fh.write('spam and eggs')
+
+ with portalocker.Lock(tmpfile, mode='a+') as fh:
+ # Make sure we didn't overwrite the original text
+ fh.seek(0)
+ assert fh.read(13) == 'spam and eggs'
+
+ with portalocker.Lock(tmpfile, mode='w+') as fh:
+ # Make sure we truncated the file
+ assert fh.read() == ''
+
+
+def test_class(tmpfile):
+ lock = portalocker.Lock(tmpfile)
+ lock2 = portalocker.Lock(tmpfile, fail_when_locked=False, timeout=0.01)
+
+ with lock:
+ lock.acquire()
+
+ with pytest.raises(portalocker.LockException):
+ with lock2:
+ pass
+
+ with lock2:
+ pass
+
+
+def test_acquire_release(tmpfile):
+ lock = portalocker.Lock(tmpfile)
+ lock2 = portalocker.Lock(tmpfile, fail_when_locked=False)
+
+ lock.acquire() # acquire lock when nobody is using it
+ with pytest.raises(portalocker.LockException):
+ # another party should not be able to acquire the lock
+ lock2.acquire(timeout=0.01)
+
+ # re-acquire a held lock is a no-op
+ lock.acquire()
+
+ lock.release() # release the lock
+ lock.release() # second release does nothing
+
+
+def test_rlock_acquire_release_count(tmpfile):
+ lock = portalocker.RLock(tmpfile)
+ # Twice acquire
+ h = lock.acquire()
+ assert not h.closed
+ lock.acquire()
+ assert not h.closed
+
+ # Two release
+ lock.release()
+ assert not h.closed
+ lock.release()
+ assert h.closed
+
+
+def test_rlock_acquire_release(tmpfile):
+ lock = portalocker.RLock(tmpfile)
+ lock2 = portalocker.RLock(tmpfile, fail_when_locked=False)
+
+ lock.acquire() # acquire lock when nobody is using it
+ with pytest.raises(portalocker.LockException):
+ # another party should not be able to acquire the lock
+ lock2.acquire(timeout=0.01)
+
+ # Now acquire again
+ lock.acquire()
+
+ lock.release() # release the lock
+ lock.release() # second release does nothing
+
+
+def test_release_unacquired(tmpfile):
+ with pytest.raises(portalocker.LockException):
+ portalocker.RLock(tmpfile).release()
+
+
+def test_exlusive(tmpfile):
+ with open(tmpfile, 'w') as fh:
+ fh.write('spam and eggs')
+
+ fh = open(tmpfile, 'r')
+ portalocker.lock(fh, portalocker.LOCK_EX | portalocker.LOCK_NB)
+
+ # Make sure we can't read the locked file
+ with pytest.raises(portalocker.LockException):
+ with open(tmpfile, 'r') as fh2:
+ portalocker.lock(fh2, portalocker.LOCK_EX | portalocker.LOCK_NB)
+ fh2.read()
+
+ # Make sure we can't write the locked file
+ with pytest.raises(portalocker.LockException):
+ with open(tmpfile, 'w+') as fh2:
+ portalocker.lock(fh2, portalocker.LOCK_EX | portalocker.LOCK_NB)
+ fh2.write('surprise and fear')
+
+ # Make sure we can explicitly unlock the file
+ portalocker.unlock(fh)
+ fh.close()
+
+
+def test_shared(tmpfile):
+ with open(tmpfile, 'w') as fh:
+ fh.write('spam and eggs')
+
+ f = open(tmpfile, 'r')
+ portalocker.lock(f, portalocker.LOCK_SH | portalocker.LOCK_NB)
+
+ # Make sure we can read the locked file
+ with open(tmpfile, 'r') as fh2:
+ portalocker.lock(fh2, portalocker.LOCK_SH | portalocker.LOCK_NB)
+ assert fh2.read() == 'spam and eggs'
+
+ # Make sure we can't write the locked file
+ with pytest.raises(portalocker.LockException):
+ with open(tmpfile, 'w+') as fh2:
+ portalocker.lock(fh2, portalocker.LOCK_EX | portalocker.LOCK_NB)
+ fh2.write('surprise and fear')
+
+ # Make sure we can explicitly unlock the file
+ portalocker.unlock(f)
+ f.close()
+
diff --git a/contrib/python/portalocker/py2/tests/ya.make b/contrib/python/portalocker/py2/tests/ya.make
new file mode 100644
index 00000000000..6917e170a2a
--- /dev/null
+++ b/contrib/python/portalocker/py2/tests/ya.make
@@ -0,0 +1,20 @@
+PY2TEST()
+
+SUBSCRIBER(g:python-contrib)
+
+NO_LINT()
+
+PEERDIR(
+ contrib/python/portalocker
+)
+
+TEST_SRCS(
+ __init__.py
+ conftest.py
+ temporary_file_lock.py
+ # Tests intallation.
+ # test_combined.py
+ tests.py
+)
+
+END()
diff --git a/contrib/python/portalocker/py2/ya.make b/contrib/python/portalocker/py2/ya.make
new file mode 100644
index 00000000000..bad9595eaf9
--- /dev/null
+++ b/contrib/python/portalocker/py2/ya.make
@@ -0,0 +1,33 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+SUBSCRIBER(g:python-contrib)
+
+VERSION(1.7.1)
+
+LICENSE(PSF-2.0)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ portalocker/__about__.py
+ portalocker/__init__.py
+ portalocker/constants.py
+ portalocker/exceptions.py
+ portalocker/portalocker.py
+ portalocker/utils.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/portalocker/py2/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
+
+RECURSE(
+ tests
+)