From 530984da358120206057d4d7667f47a546267b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Sun, 26 Dec 2021 16:07:24 +0000 Subject: [PATCH] Drop support for python3.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And start testing with 3.11. Signed-off-by: Bernát Gábor --- .github/workflows/check.yml | 2 +- .pre-commit-config.yaml | 2 +- docs/changelog.rst | 6 +++++- setup.cfg | 4 ++-- setup.py | 2 ++ src/filelock/__init__.py | 9 +++++---- src/filelock/_api.py | 32 ++++++++++++++++--------------- src/filelock/_error.py | 3 +++ src/filelock/_soft.py | 2 ++ src/filelock/_unix.py | 2 ++ src/filelock/_util.py | 2 ++ src/filelock/_windows.py | 2 ++ tests/test_filelock.py | 38 +++++++++++++++++++------------------ tox.ini | 6 +++--- 14 files changed, 67 insertions(+), 45 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2e4487a..1d5e26b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -24,11 +24,11 @@ jobs: fail-fast: false matrix: py: + - "3.11-dev" - "3.10" - "3.9" - "3.8" - "3.7" - - "3.6" - "pypy-3.7-v7.3.7" os: - ubuntu-20.04 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b54cc3..b2889d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: rev: v1.20.0 hooks: - id: setup-cfg-fmt - args: [ --min-py3-version, "3.6", "--max-py-version", "3.10" ] + args: [ --min-py3-version, "3.7", "--max-py-version", "3.11" ] - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: diff --git a/docs/changelog.rst b/docs/changelog.rst index 9600a17..acd9c56 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,11 @@ Changelog ========= -v3.4.1 (2021-13-16) +v3.4.2 (2021-12-16) +------------------- +- Drop support for python ``3.6`` + +v3.4.1 (2021-12-16) ------------------- - Add ``stacklevel`` to deprecation warnings for argument name change diff --git a/setup.cfg b/setup.cfg index 2036cea..00fffdb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,11 +16,11 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - 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 :: 3.11 Topic :: Internet Topic :: Software Development :: Libraries Topic :: System @@ -31,7 +31,7 @@ project_urls = [options] packages = find: -python_requires = >=3.6 +python_requires = >=3.7 package_dir = =src zip_safe = True diff --git a/setup.py b/setup.py index 6068493..a03590f 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from setuptools import setup setup() diff --git a/src/filelock/__init__.py b/src/filelock/__init__.py index b0f5052..afcdb70 100644 --- a/src/filelock/__init__.py +++ b/src/filelock/__init__.py @@ -5,9 +5,10 @@ :no-value: """ +from __future__ import annotations + import sys import warnings -from typing import Type from ._api import AcquireReturnProxy, BaseFileLock from ._error import Timeout @@ -21,10 +22,10 @@ if sys.platform == "win32": # pragma: win32 cover - _FileLock: Type[BaseFileLock] = WindowsFileLock + _FileLock: type[BaseFileLock] = WindowsFileLock else: # pragma: win32 no cover if has_fcntl: - _FileLock: Type[BaseFileLock] = UnixFileLock + _FileLock: type[BaseFileLock] = UnixFileLock else: _FileLock = SoftFileLock if warnings is not None: @@ -32,7 +33,7 @@ #: Alias for the lock, which should be used for the current platform. On Windows, this is an alias for # :class:`WindowsFileLock`, on Unix for :class:`UnixFileLock` and otherwise for :class:`SoftFileLock`. -FileLock: Type[BaseFileLock] = _FileLock +FileLock: type[BaseFileLock] = _FileLock __all__ = [ diff --git a/src/filelock/_api.py b/src/filelock/_api.py index 9595648..3551d5d 100644 --- a/src/filelock/_api.py +++ b/src/filelock/_api.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import time @@ -5,7 +7,7 @@ from abc import ABC, abstractmethod from threading import Lock from types import TracebackType -from typing import Any, Optional, Type, Union +from typing import Any from ._error import Timeout @@ -18,17 +20,17 @@ class AcquireReturnProxy: """A context aware object that will release the lock file when exiting.""" - def __init__(self, lock: "BaseFileLock") -> None: + def __init__(self, lock: BaseFileLock) -> None: self.lock = lock - def __enter__(self) -> "BaseFileLock": + def __enter__(self) -> BaseFileLock: return self.lock def __exit__( self, - exc_type: Optional[Type[BaseException]], # noqa: U100 - exc_value: Optional[BaseException], # noqa: U100 - traceback: Optional[TracebackType], # noqa: U100 + exc_type: type[BaseException] | None, # noqa: U100 + exc_value: BaseException | None, # noqa: U100 + traceback: TracebackType | None, # noqa: U100 ) -> None: self.lock.release() @@ -36,7 +38,7 @@ def __exit__( class BaseFileLock(ABC): """Abstract base class for a file lock object.""" - def __init__(self, lock_file: Union[str, "os.PathLike[Any]"], timeout: float = -1) -> None: + def __init__(self, lock_file: str | os.PathLike[Any], timeout: float = -1) -> None: """ Create a new lock object. @@ -50,7 +52,7 @@ def __init__(self, lock_file: Union[str, "os.PathLike[Any]"], timeout: float = - # The file descriptor for the *_lock_file* as it is returned by the os.open() function. # This file lock is only NOT None, if the object currently holds the lock. - self._lock_file_fd: Optional[int] = None + self._lock_file_fd: int | None = None # The default timeout value. self.timeout: float = timeout @@ -77,7 +79,7 @@ def timeout(self) -> float: return self._timeout @timeout.setter - def timeout(self, value: Union[float, str]) -> None: + def timeout(self, value: float | str) -> None: """ Change the default timeout value. @@ -109,10 +111,10 @@ def is_locked(self) -> bool: def acquire( self, - timeout: Optional[float] = None, + timeout: float | None = None, poll_interval: float = 0.05, *, - poll_intervall: Optional[float] = None, + poll_intervall: float | None = None, ) -> AcquireReturnProxy: """ Try to acquire the file lock. @@ -202,7 +204,7 @@ def release(self, force: bool = False) -> None: self._lock_counter = 0 _LOGGER.debug("Lock %s released on %s", lock_id, lock_filename) - def __enter__(self) -> "BaseFileLock": + def __enter__(self) -> BaseFileLock: """ Acquire the lock. @@ -213,9 +215,9 @@ def __enter__(self) -> "BaseFileLock": def __exit__( self, - exc_type: Optional[Type[BaseException]], # noqa: U100 - exc_value: Optional[BaseException], # noqa: U100 - traceback: Optional[TracebackType], # noqa: U100 + exc_type: type[BaseException] | None, # noqa: U100 + exc_value: BaseException | None, # noqa: U100 + traceback: TracebackType | None, # noqa: U100 ) -> None: """ Release the lock. diff --git a/src/filelock/_error.py b/src/filelock/_error.py index 406a476..b388521 100644 --- a/src/filelock/_error.py +++ b/src/filelock/_error.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class Timeout(TimeoutError): """Raised when the lock could not be acquired in *timeout* seconds.""" diff --git a/src/filelock/_soft.py b/src/filelock/_soft.py index 9274374..cb09799 100644 --- a/src/filelock/_soft.py +++ b/src/filelock/_soft.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from errno import EACCES, EEXIST, ENOENT diff --git a/src/filelock/_unix.py b/src/filelock/_unix.py index 02e24ff..e988380 100644 --- a/src/filelock/_unix.py +++ b/src/filelock/_unix.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from abc import ABC diff --git a/src/filelock/_util.py b/src/filelock/_util.py index 05f76f8..238b80f 100644 --- a/src/filelock/_util.py +++ b/src/filelock/_util.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import stat diff --git a/src/filelock/_windows.py b/src/filelock/_windows.py index a163202..affd93e 100644 --- a/src/filelock/_windows.py +++ b/src/filelock/_windows.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from abc import ABC diff --git a/tests/test_filelock.py b/tests/test_filelock.py index ff2a373..6991137 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import sys import threading @@ -6,7 +8,7 @@ from pathlib import Path, PurePath from stat import S_IWGRP, S_IWOTH, S_IWUSR from types import TracebackType -from typing import Callable, Iterator, Optional, Tuple, Type, Union +from typing import Callable, Iterator, Tuple, Type, Union import pytest from _pytest.logging import LogCaptureFixture @@ -26,7 +28,7 @@ ], ) def test_simple( - lock_type: Type[BaseFileLock], path_type: Union[Type[str], Type[Path]], tmp_path: Path, caplog: LogCaptureFixture + lock_type: type[BaseFileLock], path_type: type[str] | type[Path], tmp_path: Path, caplog: LogCaptureFixture ) -> None: caplog.set_level(logging.DEBUG) @@ -65,7 +67,7 @@ def tmp_path_ro(tmp_path: Path) -> Iterator[Path]: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) @pytest.mark.skipif(sys.platform == "win32", reason="Windows does not have read only folders") -def test_ro_folder(lock_type: Type[BaseFileLock], tmp_path_ro: Path) -> None: +def test_ro_folder(lock_type: type[BaseFileLock], tmp_path_ro: Path) -> None: lock = lock_type(str(tmp_path_ro / "a")) with pytest.raises(PermissionError, match="Permission denied"): lock.acquire() @@ -80,14 +82,14 @@ def tmp_file_ro(tmp_path: Path) -> Iterator[Path]: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_ro_file(lock_type: Type[BaseFileLock], tmp_file_ro: Path) -> None: +def test_ro_file(lock_type: type[BaseFileLock], tmp_file_ro: Path) -> None: lock = lock_type(str(tmp_file_ro)) with pytest.raises(PermissionError, match="Permission denied"): lock.acquire() @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_missing_directory(lock_type: Type[BaseFileLock], tmp_path_ro: Path) -> None: +def test_missing_directory(lock_type: type[BaseFileLock], tmp_path_ro: Path) -> None: lock_path = tmp_path_ro / "a" / "b" lock = lock_type(str(lock_path)) @@ -96,7 +98,7 @@ def test_missing_directory(lock_type: Type[BaseFileLock], tmp_path_ro: Path) -> @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_nested_context_manager(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_nested_context_manager(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # lock is not released before the most outer with statement that locked the lock, is left lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) @@ -119,7 +121,7 @@ def test_nested_context_manager(lock_type: Type[BaseFileLock], tmp_path: Path) - @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_nested_acquire(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_nested_acquire(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # lock is not released before the most outer with statement that locked the lock, is left lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) @@ -142,7 +144,7 @@ def test_nested_acquire(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_nested_forced_release(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_nested_forced_release(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # acquires the lock using a with-statement and releases the lock before leaving the with-statement lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) @@ -164,7 +166,7 @@ def test_nested_forced_release(lock_type: Type[BaseFileLock], tmp_path: Path) -> class ExThread(threading.Thread): def __init__(self, target: Callable[[], None], name: str) -> None: super().__init__(target=target, name=name) - self.ex: Optional[_ExcInfoType] = None + self.ex: _ExcInfoType | None = None def run(self) -> None: try: @@ -172,7 +174,7 @@ def run(self) -> None: except Exception: # pragma: no cover self.ex = sys.exc_info() # pragma: no cover - def join(self, timeout: Optional[float] = None) -> None: + def join(self, timeout: float | None = None) -> None: super().join(timeout=timeout) if self.ex is not None: print(f"fail from thread {self.name}") # pragma: no cover @@ -180,7 +182,7 @@ def join(self, timeout: Optional[float] = None) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_threaded_shared_lock_obj(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_threaded_shared_lock_obj(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # Runs 100 threads, which need the filelock. The lock must be acquired if at least one thread required it and # released, as soon as all threads stopped. lock_path = tmp_path / "a" @@ -202,7 +204,7 @@ def thread_work() -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) @pytest.mark.skipif(hasattr(sys, "pypy_version_info") and sys.platform == "win32", reason="deadlocks randomly") -def test_threaded_lock_different_lock_obj(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_threaded_lock_different_lock_obj(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # Runs multiple threads, which acquire the same lock file with a different FileLock object. When thread group 1 # acquired the lock, thread group 2 must not hold their lock. @@ -234,7 +236,7 @@ def t_2() -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_timeout(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # raises Timeout error when the lock cannot be acquired lock_path = tmp_path / "a" lock_1, lock_2 = lock_type(str(lock_path)), lock_type(str(lock_path)) @@ -257,7 +259,7 @@ def test_timeout(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_default_timeout(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_default_timeout(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # test if the default timeout parameter works lock_path = tmp_path / "a" lock_1, lock_2 = lock_type(str(lock_path)), lock_type(str(lock_path), timeout=0.1) @@ -289,7 +291,7 @@ def test_default_timeout(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_context_release_on_exc(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_context_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # lock is released when an exception is thrown in a with-statement lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) @@ -304,7 +306,7 @@ def test_context_release_on_exc(lock_type: Type[BaseFileLock], tmp_path: Path) - @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_acquire_release_on_exc(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_acquire_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # lock is released when an exception is thrown in a acquire statement lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) @@ -320,7 +322,7 @@ def test_acquire_release_on_exc(lock_type: Type[BaseFileLock], tmp_path: Path) - @pytest.mark.skipif(hasattr(sys, "pypy_version_info"), reason="del() does not trigger GC in PyPy") @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_del(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_del(lock_type: type[BaseFileLock], tmp_path: Path) -> None: # lock is released when the object is deleted lock_path = tmp_path / "a" lock_1, lock_2 = lock_type(str(lock_path)), lock_type(str(lock_path)) @@ -354,7 +356,7 @@ def test_cleanup_soft_lock(tmp_path: Path) -> None: @pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock]) -def test_poll_intervall_deprecated(lock_type: Type[BaseFileLock], tmp_path: Path) -> None: +def test_poll_intervall_deprecated(lock_type: type[BaseFileLock], tmp_path: Path) -> None: lock_path = tmp_path / "a" lock = lock_type(str(lock_path)) diff --git a/tox.ini b/tox.ini index 7e577f3..578074a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] envlist = fix_lint + py311 py310 py39 py38 py37 - py36 pypy3 type coverage @@ -50,7 +50,7 @@ description = run type check on code base setenv = {tty:MYPY_FORCE_COLOR = 1} deps = - mypy==0.910 + mypy==0.930 commands = mypy --strict src/filelock mypy --strict tests @@ -75,11 +75,11 @@ commands = coverage html -d {toxworkdir}/htmlcov diff-cover --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml depends = + py311 py310 py39 py38 py37 - py36 pypy3 [testenv:docs]