diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..4c133ca4d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +relative_files = True + + +[paths] +source = + multidict/ + multidict\ \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8a2377aa..5f64f418f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,11 +105,11 @@ jobs: COLOR: 'yes' run: | python -m pytest tests -vv - python -m coverage xml - name: Prepare coverage artifact - uses: aio-libs/prepare-coverage@v22.1.0 - with: - key: ${{ matrix.pyver }}-${{ matrix.os }}-${{ matrix.no-extensions }} + # combining Linux and Windows paths is tricky, left this excercise for others + # multidict has no Winows or MacOS specific code paths anyway + if: ${{ matrix.os == 'ubuntu' }} + uses: aio-libs/prepare-coverage@v22.1.2 test-summary: name: Tests status @@ -121,8 +121,10 @@ jobs: uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} + - name: Checkout + uses: actions/checkout@v2.4.0 - name: Upload coverage - uses: aio-libs/upload-coverage@v21.9.4 + uses: aio-libs/upload-coverage@v22.1.4 pre-deploy: name: Pre-Deploy diff --git a/CHANGES/678.bugfix b/CHANGES/678.bugfix new file mode 100644 index 000000000..b055fcab6 --- /dev/null +++ b/CHANGES/678.bugfix @@ -0,0 +1,2 @@ +Support Multidict[int] for pure-python version. +``__class_getitem__`` is already provided by C Extension, making it work with the pure-extension too. \ No newline at end of file diff --git a/multidict/__init__.py b/multidict/__init__.py index 98cf67706..e5c557a48 100644 --- a/multidict/__init__.py +++ b/multidict/__init__.py @@ -6,7 +6,7 @@ """ from ._abc import MultiMapping, MutableMultiMapping -from ._compat import USE_CYTHON_EXTENSIONS +from ._compat import USE_EXTENSIONS __all__ = ( "MultiMapping", @@ -24,7 +24,7 @@ try: - if not USE_CYTHON_EXTENSIONS: + if not USE_EXTENSIONS: raise ImportError from ._multidict import ( CIMultiDict, diff --git a/multidict/_compat.py b/multidict/_compat.py index e65912455..d1ff392b2 100644 --- a/multidict/_compat.py +++ b/multidict/_compat.py @@ -5,10 +5,10 @@ PYPY = platform.python_implementation() == "PyPy" -USE_CYTHON_EXTENSIONS = USE_CYTHON = not NO_EXTENSIONS and not PYPY +USE_EXTENSIONS = not NO_EXTENSIONS and not PYPY -if USE_CYTHON_EXTENSIONS: +if USE_EXTENSIONS: try: from . import _multidict # noqa except ImportError: - USE_CYTHON_EXTENSIONS = USE_CYTHON = False + USE_EXTENSIONS = False diff --git a/multidict/_multidict_py.py b/multidict/_multidict_py.py index 52002c0d4..bb7c4efab 100644 --- a/multidict/_multidict_py.py +++ b/multidict/_multidict_py.py @@ -1,4 +1,5 @@ import sys +import types from array import array from collections import abc @@ -6,6 +7,12 @@ _marker = object() +if sys.version_info >= (3, 9): + GenericAlias = types.GenericAlias +else: + def GenericAlias(cls): + return cls + class istr(str): @@ -130,6 +137,8 @@ def __repr__(self): body = ", ".join("'{}': {!r}".format(k, v) for k, v in self.items()) return "<{}({})>".format(self.__class__.__name__, body) + __class_getitem__ = classmethod(GenericAlias) + class MultiDictProxy(_Base, MultiMapping): """Read-only proxy for MultiDict instance.""" diff --git a/tests/conftest.py b/tests/conftest.py index 67c9ae06d..bd3b4de02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,12 +2,12 @@ import pytest -from multidict._compat import USE_CYTHON_EXTENSIONS +from multidict._compat import USE_EXTENSIONS OPTIONAL_CYTHON = ( () - if USE_CYTHON_EXTENSIONS - else pytest.mark.skip(reason="No Cython extensions available") + if USE_EXTENSIONS + else pytest.mark.skip(reason="No extensions available") ) diff --git a/tests/gen_pickles.py b/tests/gen_pickles.py index af171e0ca..028a01f8c 100644 --- a/tests/gen_pickles.py +++ b/tests/gen_pickles.py @@ -1,6 +1,6 @@ import pickle -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as PyCIMultiDict # noqa from multidict._multidict_py import MultiDict as PyMultiDict # noqa @@ -21,8 +21,8 @@ def write(name, proto): def generate(): - if not USE_CYTHON: - raise RuntimeError("Cython is required") + if not USE_EXTENSIONS: + raise RuntimeError("C Extension is required") for proto in range(pickle.HIGHEST_PROTOCOL + 1): for name in ("MultiDict", "CIMultiDict", "PyMultiDict", "PyCIMultiDict"): write(name, proto) diff --git a/tests/test_abc.py b/tests/test_abc.py index da3aa52e6..4636b3bc1 100644 --- a/tests/test_abc.py +++ b/tests/test_abc.py @@ -3,13 +3,13 @@ import pytest from multidict import MultiMapping, MutableMultiMapping -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as PyCIMultiDict from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import ( # type: ignore CIMultiDict, CIMultiDictProxy, @@ -19,9 +19,9 @@ @pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_CYTHON else []) + params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_CYTHON else []) + ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict", "PyCIMultiDict"], ) def cls(request): @@ -31,11 +31,11 @@ def cls(request): @pytest.fixture( params=( [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_CYTHON + if USE_EXTENSIONS else [] ) + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_CYTHON else []) + ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) + ["PyMultiDictProxy", "PyCIMultiDictProxy"], ) def proxy_classes(request): diff --git a/tests/test_copy.py b/tests/test_copy.py index 1a36ffcc0..564cdde59 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -2,13 +2,13 @@ import pytest -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as PyCIMultiDict from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import ( # type: ignore CIMultiDict, CIMultiDictProxy, @@ -18,9 +18,9 @@ @pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_CYTHON else []) + params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_CYTHON else []) + ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict", "PyCIMultiDict"], ) def cls(request): @@ -30,11 +30,11 @@ def cls(request): @pytest.fixture( params=( [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_CYTHON + if USE_EXTENSIONS else [] ) + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_CYTHON else []) + ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) + ["PyMultiDictProxy", "PyCIMultiDictProxy"], ) def proxy_classes(request): diff --git a/tests/test_guard.py b/tests/test_guard.py index 6f34be9c0..823cc1afb 100644 --- a/tests/test_guard.py +++ b/tests/test_guard.py @@ -1,15 +1,15 @@ import pytest -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import MultiDict # type: ignore @pytest.fixture( - params=([MultiDict] if USE_CYTHON else []) + [PyMultiDict], - ids=(["MultiDict"] if USE_CYTHON else []) + ["PyMultiDict"], + params=([MultiDict] if USE_EXTENSIONS else []) + [PyMultiDict], + ids=(["MultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict"], ) def cls(request): return request.param diff --git a/tests/test_istr.py b/tests/test_istr.py index ebdac6447..caae397f2 100644 --- a/tests/test_istr.py +++ b/tests/test_istr.py @@ -4,10 +4,10 @@ import pytest -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import istr as _istr # noqa: E402 -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import istr # type: ignore else: from multidict import istr @@ -77,7 +77,7 @@ def test_leak(self): assert abs(cnt - cnt2) < 10 # on PyPy these numbers are not equal -if USE_CYTHON: +if USE_EXTENSIONS: class TestIStr(IStrMixin): cls = istr diff --git a/tests/test_mypy.py b/tests/test_mypy.py index 96a151522..fc0522a9f 100644 --- a/tests/test_mypy.py +++ b/tests/test_mypy.py @@ -20,18 +20,13 @@ def test_classes_not_abstract() -> None: @pytest.mark.skipif( - sys.version_info >= (3, 9), - reason="Python 3.9 GenericAlias cannot be used in isinstance()/issubclass() checks", + sys.version_info >= (3, 9), reason="Python 3.9 uses GenericAlias which is different" ) -@pytest.mark.skipif( - sys.version_info < (3, 7), - reason="Python 3.5 and 3.6 has no __class_getitem__ method", -) -def test_class_getitem(_multidict) -> None: - assert issubclass(_multidict.MultiDict[str], _multidict.MultiDict) - assert issubclass(_multidict.MultiDictProxy[str], _multidict.MultiDictProxy) - assert issubclass(_multidict.CIMultiDict[str], _multidict.CIMultiDict) - assert issubclass(_multidict.CIMultiDictProxy[str], _multidict.CIMultiDictProxy) +def test_generic_exists(_multidict) -> None: + assert _multidict.MultiDict[int] is _multidict.MultiDict + assert _multidict.MultiDictProxy[int] is _multidict.MultiDictProxy + assert _multidict.CIMultiDict[int] is _multidict.CIMultiDict + assert _multidict.CIMultiDictProxy[int] is _multidict.CIMultiDictProxy @pytest.mark.skipif( diff --git a/tests/test_pickle.py b/tests/test_pickle.py index 9d729101f..3a85956e9 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -3,13 +3,13 @@ import pytest -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as PyCIMultiDict from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import ( # type: ignore CIMultiDict, CIMultiDictProxy, @@ -22,7 +22,7 @@ @pytest.fixture( - params=(["MultiDict", "CIMultiDict"] if USE_CYTHON else []) + params=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict", "PyCIMultiDict"] ) def cls_name(request): @@ -30,9 +30,9 @@ def cls_name(request): @pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_CYTHON else []) + params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_CYTHON else []) + ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict", "PyCIMultiDict"], ) def cls(request): @@ -42,11 +42,11 @@ def cls(request): @pytest.fixture( params=( [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_CYTHON + if USE_EXTENSIONS else [] ) + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_CYTHON else []) + ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) + ["PyMultiDictProxy", "PyCIMultiDictProxy"], ) def proxy_classes(request): diff --git a/tests/test_update.py b/tests/test_update.py index 142b3fdfd..4bacdbce7 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -2,18 +2,18 @@ import pytest -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as PyCIMultiDict from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import CIMultiDict, MultiDict # type: ignore @pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_CYTHON else []) + params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_CYTHON else []) + ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict", "PyCIMultiDict"], ) def cls(request): diff --git a/tests/test_version.py b/tests/test_version.py index 7db1ea7ca..0dda8db65 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -3,12 +3,12 @@ import pytest from multidict import MultiMapping -from multidict._compat import USE_CYTHON +from multidict._compat import USE_EXTENSIONS from multidict._multidict_py import CIMultiDict as _CIMultiDict from multidict._multidict_py import MultiDict as _MultiDict # noqa: E402 from multidict._multidict_py import getversion as _getversion -if USE_CYTHON: +if USE_EXTENSIONS: from multidict._multidict import ( # type: ignore CIMultiDict, MultiDict, @@ -163,7 +163,7 @@ def test_popitem_key_error(self): assert self.getver(m) == v -if USE_CYTHON: +if USE_EXTENSIONS: class TestMultiDict(VersionMixin): @@ -173,7 +173,7 @@ def getver(self, md): return getversion(md) -if USE_CYTHON: +if USE_EXTENSIONS: class TestCIMultiDict(VersionMixin):