Skip to content

Commit

Permalink
Make portalocker optional
Browse files Browse the repository at this point in the history
  • Loading branch information
rayluo committed Oct 20, 2022
1 parent ee452ea commit 323ac39
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 17 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, 2.7]
python-version: [3.7, 3.8, 3.9, "3.10"]
os: [ubuntu-latest, windows-latest, macos-latest]
include:
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-environment-variables-in-a-matrix
Expand All @@ -26,8 +26,8 @@ jobs:
toxenv: "py38"
- python-version: 3.9
toxenv: "py39"
- python-version: 2.7
toxenv: "py27"
- python-version: "3.10"
toxenv: "py310"
- python-version: 3.9
os: ubuntu-latest
lint: "true"
Expand Down
5 changes: 4 additions & 1 deletion msal_extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
KeychainPersistence,
LibsecretPersistence,
)
from .cache_lock import CrossPlatLock
try:
from .cache_lock import CrossPlatLock, LockError # It needs portalocker
except ImportError:
from .filelock import CrossPlatLock, LockError
from .token_cache import PersistedTokenCache

5 changes: 4 additions & 1 deletion msal_extensions/cache_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import logging
from distutils.version import LooseVersion

import portalocker
import portalocker # pylint: disable=import-error


logger = logging.getLogger(__name__)


LockError = portalocker.exceptions.LockException


class CrossPlatLock(object):
"""Offers a mechanism for waiting until another process is finished interacting with a shared
resource. This is specifically written to interact with a class of the same name in the .NET
Expand Down
62 changes: 62 additions & 0 deletions msal_extensions/filelock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""A cross-process lock based on exclusive creation of a given file name"""
import os
import sys
import errno
import time
import logging


logger = logging.getLogger(__name__)


class LockError(RuntimeError):
"""It will be raised when unable to obtain a lock"""


class CrossPlatLock(object):
"""This implementation relies only on ``open(..., 'x')``"""
def __init__(self, lockfile_path):
self._lockpath = lockfile_path

def __enter__(self):
self._create_lock_file('{} {}'.format(
os.getpid(),
sys.argv[0],
).encode('utf-8')) # pylint: disable=consider-using-f-string
return self

def _create_lock_file(self, content):
timeout = 5
check_interval = 0.25
current_time = getattr(time, "monotonic", time.time)
timeout_end = current_time() + timeout
while timeout_end > current_time():
try:
with open(self._lockpath, 'xb') as lock_file: # pylint: disable=unspecified-encoding
lock_file.write(content)
return None # Happy path
except ValueError: # This needs to be the first clause, for Python 2 to hit it
raise LockError("Python 2 does not support atomic creation of file")
except FileExistsError: # Only Python 3 will reach this clause
logger.debug(
"Process %d found existing lock file, will retry after %f second",
os.getpid(), check_interval)
time.sleep(check_interval)
raise LockError(
"Unable to obtain lock, despite trying for {} second(s). "
"You may want to manually remove the stale lock file {}".format(
timeout,
self._lockpath,
))

def __exit__(self, *args):
try:
os.remove(self._lockpath)
except OSError as ex: # pylint: disable=invalid-name
if ex.errno in (errno.ENOENT, errno.EACCES):
# Probably another process has raced this one
# and ended up clearing or locking the file for itself.
logger.debug("Unable to remove lock file")
else:
raise

5 changes: 4 additions & 1 deletion msal_extensions/token_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

import msal

from .cache_lock import CrossPlatLock
try:
from .cache_lock import CrossPlatLock # It needs portalocker
except ImportError:
from .filelock import CrossPlatLock
from .persistence import _mkdir_p, PersistenceNotFound


Expand Down
14 changes: 8 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
package_data={'': ['LICENSE']},
install_requires=[
'msal>=0.4.1,<2.0.0',

"pathlib2;python_version<'3.0'",
## We choose to NOT define a hard dependency on this.
# "pygobject>=3,<4;platform_system=='Linux'",
],
extras_require={
"portalocker": [
# In order to implement these requirements:
# Lowerbound = (1.6 if playform_system == 'Windows' else 1.0)
# Upperbound < (3 if python_version >= '3.5' else 2)
Expand All @@ -32,10 +37,7 @@
"portalocker<2,>=1.0;python_version=='2.7' and platform_system!='Windows'",
"portalocker<3,>=1.6;python_version>='3.5' and platform_system=='Windows'",
"portalocker<2,>=1.6;python_version=='2.7' and platform_system=='Windows'",

"pathlib2;python_version<'3.0'",
## We choose to NOT define a hard dependency on this.
# "pygobject>=3,<4;platform_system=='Linux'",
],
],
},
tests_require=['pytest'],
)
7 changes: 4 additions & 3 deletions tests/cache_file_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import sys
import time

from portalocker import exceptions
from msal_extensions import FilePersistence, CrossPlatLock, LockError

from msal_extensions import FilePersistence, CrossPlatLock

print("Testing with {}".format(CrossPlatLock))


def _acquire_lock_and_write_to_cache(cache_location, sleep_interval):
Expand All @@ -31,7 +32,7 @@ def _acquire_lock_and_write_to_cache(cache_location, sleep_interval):
time.sleep(sleep_interval)
data += "> " + str(os.getpid()) + "\n"
cache_accessor.save(data)
except exceptions.LockException as e:
except LockError as e:
logging.warning("Unable to acquire lock %s", e)


Expand Down
3 changes: 2 additions & 1 deletion tests/test_crossplatlock.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from msal_extensions.cache_lock import CrossPlatLock
from msal_extensions import CrossPlatLock


def test_ensure_file_deleted():
Expand All @@ -10,6 +10,7 @@ def test_ensure_file_deleted():
except NameError:
FileNotFoundError = IOError

print("Testing with {}".format(CrossPlatLock))
with CrossPlatLock(lockfile):
pass

Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py35,py36,py37,py38
envlist = py35,py36,py37,py38,py39,py310

[testenv]
deps = pytest
Expand Down

0 comments on commit 323ac39

Please sign in to comment.