Skip to content

Commit

Permalink
Merge branch 'release/2.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
wolph committed Feb 17, 2022
2 parents 0806422 + 970b981 commit 232c391
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 117 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: test

on:
push:
branches: [ develop, master ]
pull_request:
branches: [ develop ]

jobs:
build:

runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install tox
- name: Test with tox
run: |
tox
42 changes: 0 additions & 42 deletions .travis.yml

This file was deleted.

38 changes: 3 additions & 35 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -123,39 +123,7 @@ 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)
>>> portalocker.lock(file, portalocker.LockFlags.EXCLUSIVE)
>>> file.seek(12)
>>> file.write('foo')
>>> file.close()
Expand Down
4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ after_test:
- "%PYTHON% setup.py sdist bdist_wheel"
- ps: "ls dist"

artifacts:
- path: dist\*
# artifacts:
# - path: dist\*

#on_success:
# - TODO: upload the content of dist/*.whl to a public wheelhouse
7 changes: 7 additions & 0 deletions msvcrt.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
LK_LOCK: int
LK_NBLCK: int
LK_NBRLCK: int
LK_RLCK: int
LK_UNLCK: int

def locking(file: int, mode: int, lock_length: int) -> int: ...
2 changes: 1 addition & 1 deletion portalocker/__about__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__package_name__ = 'portalocker'
__author__ = 'Rick van Hattem'
__email__ = 'wolph@wol.ph'
__version__ = '2.3.2'
__version__ = '2.4.0'
__description__ = '''Wraps the portalocker recipe for easy usage'''
__url__ = 'https://github.com/WoLpH/portalocker'

2 changes: 1 addition & 1 deletion portalocker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#: Current author's email address
__email__ = __about__.__email__
#: Version number
__version__ = '2.3.2'
__version__ = '2.4.0'
#: Package description for Pypi
__description__ = __about__.__description__
#: Package homepage
Expand Down
12 changes: 10 additions & 2 deletions portalocker/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
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):
Expand Down
2 changes: 1 addition & 1 deletion portalocker/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
DEFAULT_THREAD_SLEEP_TIME = 0.1


class PubSubWorkerThread(client.PubSubWorkerThread):
class PubSubWorkerThread(client.PubSubWorkerThread): # type: ignore

def run(self):
try:
Expand Down
40 changes: 29 additions & 11 deletions portalocker/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import abc
import atexit
import contextlib
import logging
import os
import pathlib
import random
import tempfile
import time
import typing
import logging
import warnings

from . import constants
from . import exceptions
Expand All @@ -28,7 +29,7 @@
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.
Expand Down Expand Up @@ -56,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
Expand Down Expand Up @@ -129,21 +131,23 @@ def acquire(
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):
Expand All @@ -152,15 +156,20 @@ def release(self):
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
Expand All @@ -185,7 +194,7 @@ def __init__(
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):
Expand All @@ -195,6 +204,11 @@ def __init__(
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
Expand All @@ -212,6 +226,10 @@ def acquire(

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:
Expand Down
11 changes: 11 additions & 0 deletions portalocker_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,14 @@ def test_shared(tmpfile):
portalocker.unlock(f)
f.close()


def test_blocking_timeout(tmpfile):
flags = portalocker.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)
1 change: 0 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ addopts =
--cov portalocker
--cov-report term-missing
--cov-report html
--flake8

flake8-ignore =
*.py W391
Expand Down
10 changes: 1 addition & 9 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
[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

0 comments on commit 232c391

Please sign in to comment.