Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Multidict[int] for pure-python version #678

Merged
merged 14 commits into from
Jan 22, 2022
Merged
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[run]
relative_files = True


[paths]
source =
multidict/
multidict\
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions CHANGES/678.bugfix
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions multidict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

from ._abc import MultiMapping, MutableMultiMapping
from ._compat import USE_CYTHON_EXTENSIONS
from ._compat import USE_EXTENSIONS

__all__ = (
"MultiMapping",
Expand All @@ -24,7 +24,7 @@


try:
if not USE_CYTHON_EXTENSIONS:
if not USE_EXTENSIONS:
raise ImportError
from ._multidict import (
CIMultiDict,
Expand Down
6 changes: 3 additions & 3 deletions multidict/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions multidict/_multidict_py.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import sys
import types
from array import array
from collections import abc

from ._abc import MultiMapping, MutableMultiMapping

_marker = object()

if sys.version_info >= (3, 9):
GenericAlias = types.GenericAlias
else:
def GenericAlias(cls):
return cls


class istr(str):

Expand Down Expand Up @@ -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."""
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)


Expand Down
6 changes: 3 additions & 3 deletions tests/gen_pickles.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions tests/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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):
Expand All @@ -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):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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):
Expand All @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_guard.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions tests/test_istr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
17 changes: 6 additions & 11 deletions tests/test_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 7 additions & 7 deletions tests/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,17 +22,17 @@


@pytest.fixture(
params=(["MultiDict", "CIMultiDict"] if USE_CYTHON else [])
params=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else [])
+ ["PyMultiDict", "PyCIMultiDict"]
)
def cls_name(request):
return request.param


@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):
Expand All @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -163,7 +163,7 @@ def test_popitem_key_error(self):
assert self.getver(m) == v


if USE_CYTHON:
if USE_EXTENSIONS:

class TestMultiDict(VersionMixin):

Expand All @@ -173,7 +173,7 @@ def getver(self, md):
return getversion(md)


if USE_CYTHON:
if USE_EXTENSIONS:

class TestCIMultiDict(VersionMixin):

Expand Down