diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84635fb..19eea87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,14 +10,14 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] include: - os: windows-latest - python-version: "3.7" + python-version: "3.8" - os: windows-latest python-version: "3.11" - os: macos-latest - python-version: "3.7" + python-version: "3.8" - os: macos-latest python-version: "3.11" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49c19ef..24e6aa5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,14 +14,14 @@ repos: hooks: - id: pyupgrade args: - - --py37-plus + - --py38-plus - repo: https://github.com/psf/black rev: 23.1.0 hooks: - id: black language_version: python3 args: - - --target-version=py37 + - --target-version=py38 - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index b1c0107..9c1cbce 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -3,6 +3,7 @@ Changelog 2.3.0 - Unreleased ------------------ +- Dropped support for Python 3.7 (:pr:`84`) `Guido Imperiale`_ - ``File.__getitem__`` now returns bytearray instead of bytes. This prevents a memcpy when deserializing numpy arrays with dask. (:pr:`74`) `Guido Imperiale`_ - Removed dependency from ``heapdict``; sped up ``LRU``. (:pr:`77`) `Guido Imperiale`_ diff --git a/setup.cfg b/setup.cfg index 2ade0d7..e4ea487 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ classifiers = Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -31,7 +30,7 @@ classifiers = packages = zict zip_safe = False # https://mypy.readthedocs.io/en/latest/installed_packages.html include_package_data = True -python_requires = >=3.7 +python_requires = >=3.8 install_requires = [options.package_data] @@ -76,7 +75,7 @@ known_first_party = zict [mypy] -# Silence errors about Python 3.9-style delayed type annotations on Python 3.7/3.8 +# Silence errors about Python 3.9-style delayed type annotations on Python 3.8 python_version = 3.9 # See https://github.com/python/mypy/issues/12286 for automatic multi-platform support platform = linux diff --git a/zict/cache.py b/zict/cache.py index 3d66c26..ba57b2a 100644 --- a/zict/cache.py +++ b/zict/cache.py @@ -107,7 +107,7 @@ def close(self) -> None: if TYPE_CHECKING: - # TODO Python 3.9: remove this branch and just use [] in the implementation below + # TODO remove this branch and just use [] in the implementation below (needs Python >=3.9) class WeakValueMapping(weakref.WeakValueDictionary[KT, VT]): ... diff --git a/zict/common.py b/zict/common.py index 698c772..ec84d1f 100644 --- a/zict/common.py +++ b/zict/common.py @@ -3,50 +3,29 @@ from collections.abc import Iterable, Mapping from itertools import chain from typing import MutableMapping # TODO move to collections.abc (needs Python >=3.9) -from typing import Any, TypeVar, overload +from typing import TYPE_CHECKING, Any, TypeVar -T = TypeVar("T") KT = TypeVar("KT") VT = TypeVar("VT") +if TYPE_CHECKING: + # TODO import from typing (needs Python >=3.11) + from typing_extensions import Self + class ZictBase(MutableMapping[KT, VT]): """Base class for zict mappings""" - # TODO use positional-only arguments to protect self (requires Python 3.8+) - @overload # type: ignore[override] - def update(self, __m: Mapping[KT, VT], **kwargs: VT) -> None: - ... - - @overload - def update(self, __m: Iterable[tuple[KT, VT]], **kwargs: VT) -> None: - ... - - @overload - def update(self, **kwargs: VT) -> None: - ... - - def update(*args, **kwargs): # type: ignore[no-untyped-def] - # Boilerplate for implementing an update() method - if not args: - raise TypeError( - "descriptor 'update' of MutableMapping object " "needs an argument" - ) - self = args[0] - args = args[1:] - if len(args) > 1: - raise TypeError("update expected at most 1 arguments, got %d" % len(args)) - items = [] - if args: - other = args[0] - if isinstance(other, Mapping) or hasattr(other, "items"): - items = other.items() - else: - # Assuming (key, value) pairs - items = other - if kwargs: - items = chain(items, kwargs.items()) - self._do_update(items) + def update( # type: ignore[override] + self, + other: Mapping[KT, VT] | Iterable[tuple[KT, VT]] = (), + /, + **kwargs: VT, + ) -> None: + if hasattr(other, "items"): + other = other.items() + other = chain(other, kwargs.items()) # type: ignore + self._do_update(other) def _do_update(self, items: Iterable[tuple[KT, VT]]) -> None: # Default implementation, can be overriden for speed @@ -56,12 +35,15 @@ def _do_update(self, items: Iterable[tuple[KT, VT]]) -> None: def close(self) -> None: """Release any system resources held by this object""" - def __enter__(self: T) -> T: + def __enter__(self) -> Self: return self def __exit__(self, *args: Any) -> None: self.close() + def __del__(self) -> None: + self.close() + def close(*z: Any) -> None: """Close *z* if possible.""" diff --git a/zict/tests/test_common.py b/zict/tests/test_common.py new file mode 100644 index 0000000..e10f7b5 --- /dev/null +++ b/zict/tests/test_common.py @@ -0,0 +1,53 @@ +from collections import UserDict + +from zict.common import ZictBase + + +def test_close_on_del(): + closed = False + + class D(ZictBase, UserDict): + def close(self): + nonlocal closed + closed = True + + d = D() + del d + assert closed + + +def test_context(): + closed = False + + class D(ZictBase, UserDict): + def close(self): + nonlocal closed + closed = True + + d = D() + with d as d2: + assert d2 is d + assert closed + + +def test_update(): + items = [] + + class D(ZictBase, UserDict): + def _do_update(self, items_): + nonlocal items + items = items_ + + d = D() + d.update({"x": 1}) + assert list(items) == [("x", 1)] + d.update(iter([("x", 2)])) + assert list(items) == [("x", 2)] + d.update({"x": 3}, y=4) + assert list(items) == [("x", 3), ("y", 4)] + d.update(x=5) + assert list(items) == [("x", 5)] + + # Special kwargs can't overwrite positional-only parameters + d.update(self=1, other=2) + assert list(items) == [("self", 1), ("other", 2)] diff --git a/zict/zip.py b/zict/zip.py index 9f9d2da..9ae73c3 100644 --- a/zict/zip.py +++ b/zict/zip.py @@ -3,13 +3,13 @@ import zipfile from collections.abc import Iterator from typing import MutableMapping # TODO move to collections.abc (needs Python >=3.9) -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal if TYPE_CHECKING: - # TODO: move to typing on Python 3.8+ and 3.10+ respectively - from typing_extensions import Literal, TypeAlias + # TODO: import from typing (needs Python >=3.10) + from typing_extensions import TypeAlias - FileMode: TypeAlias = Literal["r", "w", "x", "a"] +FileMode: TypeAlias = Literal["r", "w", "x", "a"] class Zip(MutableMapping[str, bytes]):