Skip to content

Commit

Permalink
Support GenericAliases for Python 3.9+ (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
asvetlov committed Dec 3, 2020
1 parent e19929e commit 14f433d
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 17 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -23,10 +23,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Python 3.8
- name: Setup Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.9
- name: Cache PyPI
uses: actions/cache@v2
with:
Expand Down
1 change: 1 addition & 0 deletions CHANGES/553.feature
@@ -0,0 +1 @@
Support ``GenericAliases`` (``MultiDict[str]``) for Python 3.9+
13 changes: 11 additions & 2 deletions multidict/_abc.py
@@ -1,4 +1,6 @@
import abc
import sys
import types
from collections.abc import Mapping, MutableMapping


Expand All @@ -7,8 +9,15 @@ class _TypingMeta(abc.ABCMeta):
# basically MultiMapping[str] and other generic-like type instantiations
# are emulated.
# Note: real type hints are provided by __init__.pyi stub file
def __getitem__(self, key):
return self
if sys.version_info >= (3, 9):

def __getitem__(self, key):
return types.GenericAlias(self, key)

else:

def __getitem__(self, key):
return self


class MultiMapping(Mapping, metaclass=_TypingMeta):
Expand Down
5 changes: 5 additions & 0 deletions multidict/_multidict.c
Expand Up @@ -872,12 +872,17 @@ PyDoc_STRVAR(multidict_popitem_doc,
PyDoc_STRVAR(multidict_update_doc,
"Update the dictionary from *other*, overwriting existing keys.");


#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9
#define multidict_class_getitem Py_GenericAlias
#else
static inline PyObject *
multidict_class_getitem(PyObject *self, PyObject *arg)
{
Py_INCREF(self);
return self;
}
#endif


PyDoc_STRVAR(sizeof__doc__,
Expand Down
13 changes: 0 additions & 13 deletions tests/test_multidict.py
Expand Up @@ -71,19 +71,6 @@ def test_proxy_copy(dict_cls, proxy_cls):
assert d1 is not d2


@pytest.mark.skipif(
sys.version_info < (3, 7),
reason="__class_getitem__ is supported started from Python 3.7",
)
@pytest.mark.parametrize(
"cls",
["MultiDict", "CIMultiDict", "MultiDictProxy", "CIMultiDictProxy"],
indirect=True,
)
def test_class_getitem(cls):
assert cls[str] is cls


@pytest.mark.parametrize(
"cls",
["MultiDict", "CIMultiDict", "MultiDictProxy", "CIMultiDictProxy"],
Expand Down
37 changes: 37 additions & 0 deletions tests/test_mypy.py
@@ -1,3 +1,8 @@
import sys
import types

import pytest

import multidict


Expand All @@ -12,3 +17,35 @@ def test_classes_not_abstract() -> None:
d2.getall("a")
d3.getone("a")
d4.getall("a")


@pytest.mark.skipif(
sys.version_info >= (3, 9),
reason="Python 3.9 GenericAlias cannot be used in isinstance()/issubclass() checks",
)
@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)


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="Python 3.9 is required for GenericAlias"
)
def test_generic_alias(_multidict) -> None:

assert _multidict.MultiDict[int] == types.GenericAlias(_multidict.MultiDict, (int,))
assert _multidict.MultiDictProxy[int] == types.GenericAlias(
_multidict.MultiDictProxy, (int,)
)
assert _multidict.CIMultiDict[int] == types.GenericAlias(
_multidict.CIMultiDict, (int,)
)
assert _multidict.CIMultiDictProxy[int] == types.GenericAlias(
_multidict.CIMultiDictProxy, (int,)
)

0 comments on commit 14f433d

Please sign in to comment.