From 4f58a190c6d14d7187589adfc8d7b11eb106ede3 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 10 Jan 2024 21:01:32 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Rewire=20pytest=20fixtures=20avo?= =?UTF-8?q?iding=20import=20loops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch also refactors and reduces the duplication of the previously existing fixtures for retrieving different multidict module implementations and makes the c-extension testing controllable by a CLI option on the pytest level. Fixes #837 --- .github/workflows/ci-cd.yml | 2 +- .mypy.ini | 5 +- .pre-commit-config.yaml | 15 + setup.cfg | 9 - tests/__init__.py | 0 ...kle.0 => cimultidict-c-extension.pickle.0} | 0 ...kle.1 => cimultidict-c-extension.pickle.1} | Bin ...kle.2 => cimultidict-c-extension.pickle.2} | Bin ...kle.3 => cimultidict-c-extension.pickle.3} | Bin ...kle.4 => cimultidict-c-extension.pickle.4} | Bin ...kle.5 => cimultidict-c-extension.pickle.5} | Bin ...kle.0 => cimultidict-pure-python.pickle.0} | 0 ...kle.1 => cimultidict-pure-python.pickle.1} | Bin ...kle.2 => cimultidict-pure-python.pickle.2} | Bin ...kle.3 => cimultidict-pure-python.pickle.3} | Bin ...kle.4 => cimultidict-pure-python.pickle.4} | Bin ...kle.5 => cimultidict-pure-python.pickle.5} | Bin tests/conftest.py | 163 ++++++- tests/gen_pickles.py | 29 +- ...ickle.0 => multidict-c-extension.pickle.0} | 0 ...ickle.1 => multidict-c-extension.pickle.1} | Bin ...ickle.2 => multidict-c-extension.pickle.2} | Bin ...ickle.3 => multidict-c-extension.pickle.3} | Bin ...ickle.4 => multidict-c-extension.pickle.4} | Bin ...ickle.5 => multidict-c-extension.pickle.5} | Bin ...ickle.0 => multidict-pure-python.pickle.0} | 0 ...ickle.1 => multidict-pure-python.pickle.1} | Bin ...ickle.2 => multidict-pure-python.pickle.2} | Bin ...ickle.3 => multidict-pure-python.pickle.3} | Bin ...ickle.4 => multidict-pure-python.pickle.4} | Bin ...ickle.5 => multidict-pure-python.pickle.5} | Bin tests/test_abc.py | 50 +- tests/test_copy.py | 61 +-- tests/test_guard.py | 34 +- tests/test_multidict.py | 227 +++++---- tests/test_mutable_multidict.py | 433 ++++++++++++------ tests/test_pickle.py | 73 +-- tests/test_types.py | 95 ++-- tests/test_update.py | 110 ++--- tests/test_version.py | 409 +++++++++-------- 40 files changed, 947 insertions(+), 768 deletions(-) delete mode 100644 tests/__init__.py rename tests/{cimultidict.pickle.0 => cimultidict-c-extension.pickle.0} (100%) rename tests/{cimultidict.pickle.1 => cimultidict-c-extension.pickle.1} (100%) rename tests/{cimultidict.pickle.2 => cimultidict-c-extension.pickle.2} (100%) rename tests/{cimultidict.pickle.3 => cimultidict-c-extension.pickle.3} (100%) rename tests/{cimultidict.pickle.4 => cimultidict-c-extension.pickle.4} (100%) rename tests/{cimultidict.pickle.5 => cimultidict-c-extension.pickle.5} (100%) rename tests/{pycimultidict.pickle.0 => cimultidict-pure-python.pickle.0} (100%) rename tests/{pycimultidict.pickle.1 => cimultidict-pure-python.pickle.1} (100%) rename tests/{pycimultidict.pickle.2 => cimultidict-pure-python.pickle.2} (100%) rename tests/{pycimultidict.pickle.3 => cimultidict-pure-python.pickle.3} (100%) rename tests/{pycimultidict.pickle.4 => cimultidict-pure-python.pickle.4} (100%) rename tests/{pycimultidict.pickle.5 => cimultidict-pure-python.pickle.5} (100%) rename tests/{multidict.pickle.0 => multidict-c-extension.pickle.0} (100%) rename tests/{multidict.pickle.1 => multidict-c-extension.pickle.1} (100%) rename tests/{multidict.pickle.2 => multidict-c-extension.pickle.2} (100%) rename tests/{multidict.pickle.3 => multidict-c-extension.pickle.3} (100%) rename tests/{multidict.pickle.4 => multidict-c-extension.pickle.4} (100%) rename tests/{multidict.pickle.5 => multidict-c-extension.pickle.5} (100%) rename tests/{pymultidict.pickle.0 => multidict-pure-python.pickle.0} (100%) rename tests/{pymultidict.pickle.1 => multidict-pure-python.pickle.1} (100%) rename tests/{pymultidict.pickle.2 => multidict-pure-python.pickle.2} (100%) rename tests/{pymultidict.pickle.3 => multidict-pure-python.pickle.3} (100%) rename tests/{pymultidict.pickle.4 => multidict-pure-python.pickle.4} (100%) rename tests/{pymultidict.pickle.5 => multidict-pure-python.pickle.5} (100%) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index eae3898a4..b12723e8d 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -264,7 +264,7 @@ jobs: env: MULTIDICT_NO_EXTENSIONS: ${{ matrix.no-extensions }} run: >- - python -m pytest tests -vv + python -Im pytest tests -vv --cov-report xml --junitxml=.test-results/pytest/test.xml - name: Produce markdown test summary from JUnit diff --git a/.mypy.ini b/.mypy.ini index 5113fa062..410d17424 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -25,6 +25,5 @@ warn_unreachable = True warn_unused_ignores = True warn_return_any = True -[mypy-tests.*] -disallow_any_decorated = False -disallow_untyped_calls = False +[mypy-test_multidict] +disable_error_code = call-arg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d8116fe1..2f0dffb14 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,6 +31,21 @@ repos: - repo: local hooks: + - id: top-level-tests-init-py + name: changelog filenames + language: fail + entry: >- + The `tests/__init__.py` module must not exist so `pytest` doesn't add the + project root to `sys.path` / `$PYTHONPATH` + files: >- + (?x) + ^ + tests/__init__\.py + $ + types: [] + types_or: + - file + - symlink - id: changelogs-rst name: changelog filenames language: fail diff --git a/setup.cfg b/setup.cfg index cb95b33fd..efaf129b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,12 +67,3 @@ norecursedirs = dist build .tox docs requirements tools addopts = --doctest-modules --cov=multidict --cov-report term-missing:skip-covered --cov-report xml --junitxml=junit-test-results.xml -v doctest_optionflags = ALLOW_UNICODE ELLIPSIS junit_family = xunit2 - -[mypy] - -[mypy-pytest] -ignore_missing_imports = true - - -[mypy-multidict._multidict] -ignore_missing_imports = true diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/cimultidict.pickle.0 b/tests/cimultidict-c-extension.pickle.0 similarity index 100% rename from tests/cimultidict.pickle.0 rename to tests/cimultidict-c-extension.pickle.0 diff --git a/tests/cimultidict.pickle.1 b/tests/cimultidict-c-extension.pickle.1 similarity index 100% rename from tests/cimultidict.pickle.1 rename to tests/cimultidict-c-extension.pickle.1 diff --git a/tests/cimultidict.pickle.2 b/tests/cimultidict-c-extension.pickle.2 similarity index 100% rename from tests/cimultidict.pickle.2 rename to tests/cimultidict-c-extension.pickle.2 diff --git a/tests/cimultidict.pickle.3 b/tests/cimultidict-c-extension.pickle.3 similarity index 100% rename from tests/cimultidict.pickle.3 rename to tests/cimultidict-c-extension.pickle.3 diff --git a/tests/cimultidict.pickle.4 b/tests/cimultidict-c-extension.pickle.4 similarity index 100% rename from tests/cimultidict.pickle.4 rename to tests/cimultidict-c-extension.pickle.4 diff --git a/tests/cimultidict.pickle.5 b/tests/cimultidict-c-extension.pickle.5 similarity index 100% rename from tests/cimultidict.pickle.5 rename to tests/cimultidict-c-extension.pickle.5 diff --git a/tests/pycimultidict.pickle.0 b/tests/cimultidict-pure-python.pickle.0 similarity index 100% rename from tests/pycimultidict.pickle.0 rename to tests/cimultidict-pure-python.pickle.0 diff --git a/tests/pycimultidict.pickle.1 b/tests/cimultidict-pure-python.pickle.1 similarity index 100% rename from tests/pycimultidict.pickle.1 rename to tests/cimultidict-pure-python.pickle.1 diff --git a/tests/pycimultidict.pickle.2 b/tests/cimultidict-pure-python.pickle.2 similarity index 100% rename from tests/pycimultidict.pickle.2 rename to tests/cimultidict-pure-python.pickle.2 diff --git a/tests/pycimultidict.pickle.3 b/tests/cimultidict-pure-python.pickle.3 similarity index 100% rename from tests/pycimultidict.pickle.3 rename to tests/cimultidict-pure-python.pickle.3 diff --git a/tests/pycimultidict.pickle.4 b/tests/cimultidict-pure-python.pickle.4 similarity index 100% rename from tests/pycimultidict.pickle.4 rename to tests/cimultidict-pure-python.pickle.4 diff --git a/tests/pycimultidict.pickle.5 b/tests/cimultidict-pure-python.pickle.5 similarity index 100% rename from tests/pycimultidict.pickle.5 rename to tests/cimultidict-pure-python.pickle.5 diff --git a/tests/conftest.py b/tests/conftest.py index bd3b4de02..c74648aa4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,25 +1,164 @@ +from __future__ import annotations + +import argparse import pickle +from dataclasses import dataclass +from importlib import import_module +from sys import version_info as _version_info +from types import ModuleType +from typing import Callable, Type + +try: + from functools import cached_property # Python 3.8+ +except ImportError: + from functools import lru_cache as _lru_cache + + def cached_property(func): + return property(_lru_cache()(func)) import pytest -from multidict._compat import USE_EXTENSIONS +from multidict import MultiMapping, MutableMultiMapping + + +PY_38_AND_BELOW = _version_info < (3, 9) + + +@dataclass(frozen=True) +class MultidictImplementation: + is_pure_python: bool + + @cached_property + def tag(self) -> str: + return "pure-python" if self.is_pure_python else "c-extension" + + @cached_property + def imported_module(self) -> ModuleType: + importable_module = ( + "_multidict_py" if self.is_pure_python + else "_multidict" + ) + return import_module(f"multidict.{importable_module}") + + def __str__(self): + return f"{self.tag}-module" + -OPTIONAL_CYTHON = ( - () - if USE_EXTENSIONS - else pytest.mark.skip(reason="No extensions available") +@pytest.fixture( + scope="session", + params=( + MultidictImplementation(is_pure_python=False), + MultidictImplementation(is_pure_python=True), + ), + ids=str, ) +def multidict_implementation(request) -> MultidictImplementation: + multidict_implementation = request.param + test_c_extensions = request.config.getoption("--c-extensions") is True + + if not test_c_extensions and not multidict_implementation.is_pure_python: + pytest.skip("C-extension testing not requested") + + return multidict_implementation -@pytest.fixture( # type: ignore[call-overload] +@pytest.fixture(scope="session") +def multidict_module( + multidict_implementation: MultidictImplementation, +) -> ModuleType: + return multidict_implementation.imported_module + + +@pytest.fixture( scope="session", - params=[ - pytest.param("multidict._multidict", marks=OPTIONAL_CYTHON), # type: ignore - "multidict._multidict_py", - ], + params=("MultiDict", "CIMultiDict"), + ids=("case-sensitive", "case-insensitive"), ) -def _multidict(request): - return pytest.importorskip(request.param) +def any_multidict_class_name(request: pytest.FixtureRequest) -> str: + return request.param + + +@pytest.fixture(scope="session") +def multidict_class( + any_multidict_class_name: str, + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + return getattr(multidict_module, any_multidict_class_name) + + +@pytest.fixture(scope="session") +def case_sensitive_multidict_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + return multidict_module.MultiDict + + +@pytest.fixture(scope="session") +def case_insensitive_multidict_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + return multidict_module.CIMultiDict + + +@pytest.fixture(scope="session") +def case_insensitive_str_class(multidict_module: ModuleType) -> Type[str]: + return multidict_module.istr + + +@pytest.fixture(scope="session") +def any_multidict_proxy_class_name(any_multidict_class_name: str) -> str: + return f"{any_multidict_class_name}Proxy" + + +@pytest.fixture(scope="session") +def any_multidict_proxy_class( + any_multidict_proxy_class_name: str, + multidict_module: ModuleType, +) -> Type[MultiMapping[str]]: + return getattr(multidict_module, any_multidict_proxy_class_name) + + +@pytest.fixture(scope="session") +def case_sensitive_multidict_proxy_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + return multidict_module.MultiDictProxy + + +@pytest.fixture(scope="session") +def case_insensitive_multidict_proxy_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + return multidict_module.CIMultiDictProxy + + +@pytest.fixture(scope="session") +def multidict_getversion_callable(multidict_module: ModuleType) -> Callable: + return multidict_module.getversion + + +def pytest_addoption( + parser: pytest.Parser, + pluginmanager: pytest.PytestPluginManager, +) -> None: + del pluginmanager + parser.addoption( + "--c-extensions", # disabled with `--no-c-extensions` + action= + 'store_true' if PY_38_AND_BELOW + else argparse.BooleanOptionalAction, + default=True, + dest='c_extensions', + help="Test C-extensions (on by default)", + ) + + if PY_38_AND_BELOW: + parser.addoption( + "--no-c-extensions", + action='store_false', + dest='c_extensions', + help="Skip testing C-extensions (on by default)", + ) def pytest_generate_tests(metafunc): diff --git a/tests/gen_pickles.py b/tests/gen_pickles.py index 028a01f8c..0f4ffac0d 100644 --- a/tests/gen_pickles.py +++ b/tests/gen_pickles.py @@ -1,31 +1,24 @@ import pickle -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict # noqa -from multidict._multidict_py import MultiDict as PyMultiDict # noqa +import multidict -try: - from multidict._multidict import ( # type: ignore # noqa - CIMultiDict, - MultiDict, - ) -except ImportError: - pass - -def write(name, proto): - cls = globals()[name] +def write(tag, cls, proto): d = cls([("a", 1), ("a", 2)]) - with open("{}.pickle.{}".format(name.lower(), proto), "wb") as f: + file_basename = f"{cls.__name__.lower()}-{tag}" + with open(f"{file_basename}.pickle.{proto}", "wb") as f: pickle.dump(d, f, proto) def generate(): - if not USE_EXTENSIONS: - raise RuntimeError("C Extension is required") + _impl_map = { + "c-extension": multidict._multidict, + "pure-python": multidict._multidict_py, + } for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for name in ("MultiDict", "CIMultiDict", "PyMultiDict", "PyCIMultiDict"): - write(name, proto) + for tag, impl in _impl_map.items(): + for cls in impl.CIMultiDict, impl.MultiDict: + write(tag, cls, proto) if __name__ == "__main__": diff --git a/tests/multidict.pickle.0 b/tests/multidict-c-extension.pickle.0 similarity index 100% rename from tests/multidict.pickle.0 rename to tests/multidict-c-extension.pickle.0 diff --git a/tests/multidict.pickle.1 b/tests/multidict-c-extension.pickle.1 similarity index 100% rename from tests/multidict.pickle.1 rename to tests/multidict-c-extension.pickle.1 diff --git a/tests/multidict.pickle.2 b/tests/multidict-c-extension.pickle.2 similarity index 100% rename from tests/multidict.pickle.2 rename to tests/multidict-c-extension.pickle.2 diff --git a/tests/multidict.pickle.3 b/tests/multidict-c-extension.pickle.3 similarity index 100% rename from tests/multidict.pickle.3 rename to tests/multidict-c-extension.pickle.3 diff --git a/tests/multidict.pickle.4 b/tests/multidict-c-extension.pickle.4 similarity index 100% rename from tests/multidict.pickle.4 rename to tests/multidict-c-extension.pickle.4 diff --git a/tests/multidict.pickle.5 b/tests/multidict-c-extension.pickle.5 similarity index 100% rename from tests/multidict.pickle.5 rename to tests/multidict-c-extension.pickle.5 diff --git a/tests/pymultidict.pickle.0 b/tests/multidict-pure-python.pickle.0 similarity index 100% rename from tests/pymultidict.pickle.0 rename to tests/multidict-pure-python.pickle.0 diff --git a/tests/pymultidict.pickle.1 b/tests/multidict-pure-python.pickle.1 similarity index 100% rename from tests/pymultidict.pickle.1 rename to tests/multidict-pure-python.pickle.1 diff --git a/tests/pymultidict.pickle.2 b/tests/multidict-pure-python.pickle.2 similarity index 100% rename from tests/pymultidict.pickle.2 rename to tests/multidict-pure-python.pickle.2 diff --git a/tests/pymultidict.pickle.3 b/tests/multidict-pure-python.pickle.3 similarity index 100% rename from tests/pymultidict.pickle.3 rename to tests/multidict-pure-python.pickle.3 diff --git a/tests/pymultidict.pickle.4 b/tests/multidict-pure-python.pickle.4 similarity index 100% rename from tests/pymultidict.pickle.4 rename to tests/multidict-pure-python.pickle.4 diff --git a/tests/pymultidict.pickle.5 b/tests/multidict-pure-python.pickle.5 similarity index 100% rename from tests/pymultidict.pickle.5 rename to tests/multidict-pure-python.pickle.5 diff --git a/tests/test_abc.py b/tests/test_abc.py index 4636b3bc1..4d70cef8a 100644 --- a/tests/test_abc.py +++ b/tests/test_abc.py @@ -3,43 +3,6 @@ import pytest from multidict import MultiMapping, MutableMultiMapping -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_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param def test_abc_inheritance(): @@ -116,15 +79,14 @@ def test_abc_popall(): B().popall("key") -def test_multidict_inheritance(cls): - assert issubclass(cls, MultiMapping) - assert issubclass(cls, MutableMultiMapping) +def test_multidict_inheritance(multidict_class): + assert issubclass(multidict_class, MultiMapping) + assert issubclass(multidict_class, MutableMultiMapping) -def test_proxy_inheritance(proxy_classes): - proxy, _ = proxy_classes - assert issubclass(proxy, MultiMapping) - assert not issubclass(proxy, MutableMultiMapping) +def test_proxy_inheritance(any_multidict_proxy_class): + assert issubclass(any_multidict_proxy_class, MultiMapping) + assert not issubclass(any_multidict_proxy_class, MutableMultiMapping) def test_generic_type_in_runtime(): diff --git a/tests/test_copy.py b/tests/test_copy.py index 564cdde59..cab655191 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -1,48 +1,8 @@ import copy -import pytest -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_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param - - -def test_copy(cls): - d = cls() +def test_copy(multidict_class): + d = multidict_class() d["foo"] = 6 d2 = d.copy() d2["foo"] = 7 @@ -50,11 +10,10 @@ def test_copy(cls): assert d2["foo"] == 7 -def test_copy_proxy(proxy_classes): - proxy_cls, dict_cls = proxy_classes - d = dict_cls() +def test_copy_proxy(multidict_class, any_multidict_proxy_class): + d = multidict_class() d["foo"] = 6 - p = proxy_cls(d) + p = any_multidict_proxy_class(d) d2 = p.copy() d2["foo"] = 7 assert d["foo"] == 6 @@ -62,8 +21,8 @@ def test_copy_proxy(proxy_classes): assert d2["foo"] == 7 -def test_copy_std_copy(cls): - d = cls() +def test_copy_std_copy(multidict_class): + d = multidict_class() d["foo"] = 6 d2 = copy.copy(d) d2["foo"] = 7 @@ -71,9 +30,9 @@ def test_copy_std_copy(cls): assert d2["foo"] == 7 -def test_ci_multidict_clone(cls): - d = cls(foo=6) - d2 = cls(d) +def test_ci_multidict_clone(multidict_class): + d = multidict_class(foo=6) + d2 = multidict_class(d) d2["foo"] = 7 assert d["foo"] == 6 assert d2["foo"] == 7 diff --git a/tests/test_guard.py b/tests/test_guard.py index 823cc1afb..225da67c8 100644 --- a/tests/test_guard.py +++ b/tests/test_guard.py @@ -1,38 +1,34 @@ -import pytest - -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 - -if USE_EXTENSIONS: - from multidict._multidict import MultiDict # type: ignore +from typing import Type +import pytest -@pytest.fixture( - params=([MultiDict] if USE_EXTENSIONS else []) + [PyMultiDict], - ids=(["MultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict"], -) -def cls(request): - return request.param +from multidict import MultiMapping -def test_guard_items(cls): - md = cls({"a": "b"}) +def test_guard_items( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.items()) md["a"] = "c" with pytest.raises(RuntimeError): next(it) -def test_guard_keys(cls): - md = cls({"a": "b"}) +def test_guard_keys( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.keys()) md["a"] = "c" with pytest.raises(RuntimeError): next(it) -def test_guard_values(cls): - md = cls({"a": "b"}) +def test_guard_values( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.values()) md["a"] = "c" with pytest.raises(RuntimeError): diff --git a/tests/test_multidict.py b/tests/test_multidict.py index 706fc93e7..6dd8c1cfb 100644 --- a/tests/test_multidict.py +++ b/tests/test_multidict.py @@ -1,13 +1,15 @@ +from __future__ import annotations + import gc import operator import sys import weakref from collections import deque from collections.abc import Mapping -from functools import reduce +from types import ModuleType from typing import ( - Any, Callable, + cast, Dict, Iterable, Iterator, @@ -16,106 +18,107 @@ Set, Tuple, Type, - TypeVar, Union, ) import pytest import multidict -from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy - -_MultiDictClasses = Union[Type[MultiDict[str]], Type[CIMultiDict[str]]] +from multidict import CIMultiDict, MultiDict, MultiMapping, MutableMultiMapping def chained_callable( - module: object, callables: Union[str, Iterable[str]] -) -> Callable[..., Any]: + module: ModuleType, + callables: Iterable[str], +) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: """ - Returns callable that will get and call all given objects in module in - exact order. If `names` is a single object's name function will return - object itself. - - Will treat `names` of type `str` as a list of single element. + Return callable that will get and call all given objects in module in + exact order. """ - callables = (callables,) if isinstance(callables, str) else callables - _callable, *rest = (getattr(module, name) for name in callables) + def chained_call( + *args: object, + **kwargs: object, + ) -> MultiMapping[int | str] | MutableMultiMapping[int | str]: + nonlocal callables - def chained_call(*args: object, **kwargs: object) -> Any: - return reduce(lambda res, c: c(res), rest, _callable(*args, **kwargs)) + callable_chain = (getattr(module, name) for name in callables) + first_callable = next(callable_chain) - return chained_call if len(rest) > 0 else _callable # type: ignore[no-any-return] + value = first_callable(*args, **kwargs) + for element in callable_chain: + value = element(value) + return cast( + Union[ + MultiMapping[Union[int, str]], + MutableMultiMapping[Union[int, str]], + ], + value, + ) -@pytest.fixture(scope="function") -def cls(request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + return chained_call @pytest.fixture(scope="function") -def classes(request: Any, _multidict: Any) -> Any: - return tuple(chained_callable(_multidict, n) for n in request.param) - - -@pytest.mark.parametrize("cls", ["MultiDict", "CIMultiDict"], indirect=True) -def test_exposed_names( - cls: Union[Type[MultiDict[object]], Type[CIMultiDict[object]]] -) -> None: - name = cls.__name__ +def cls( # type: ignore[misc] + request: pytest.FixtureRequest, + multidict_module: ModuleType, +) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: + return chained_callable(multidict_module, request.param) - while name.startswith("_"): - name = name[1:] - assert name in multidict.__all__ # type: ignore[attr-defined] +def test_exposed_names(any_multidict_class_name: str) -> None: + assert any_multidict_class_name in multidict.__all__ # type: ignore[attr-defined] @pytest.mark.parametrize( - "cls, key_cls", - [("MultiDict", str), (("MultiDict", "MultiDictProxy"), str)], + ("cls", "key_cls"), + [ + (("MultiDict", ), str), + ( + ("MultiDict", "MultiDictProxy"), + str, + ), + ], indirect=["cls"], ) def test__iter__types( - cls: Type[MultiDict[Union[str, int]]], key_cls: Type[object] + cls: Type[MultiDict[Union[str, int]]], + key_cls: Type[object], ) -> None: d = cls([("key", "one"), ("key2", "two"), ("key", 3)]) for i in d: assert type(i) is key_cls, (type(i), key_cls) -_ClsPair = TypeVar( - "_ClsPair", - Tuple[Type[MultiDict[str]], Type[MultiDictProxy[str]]], - Tuple[Type[CIMultiDict[str]], Type[CIMultiDictProxy[str]]], -) - - -@pytest.mark.parametrize( - "classes", - [("MultiDict", "MultiDictProxy"), ("CIMultiDict", "CIMultiDictProxy")], - indirect=True, -) -def test_proxy_copy(classes: _ClsPair) -> None: - dict_cls, proxy_cls = classes - d1 = dict_cls(key="value", a="b") - p1 = proxy_cls(d1) +def test_proxy_copy( + multidict_class: Type[MutableMultiMapping[str]], + any_multidict_proxy_class: Type[MultiMapping[str]], +) -> None: + d1 = multidict_class(key="value", a="b") + p1 = any_multidict_proxy_class(d1) - d2 = p1.copy() + d2 = p1.copy() # type: ignore[attr-defined] assert d1 == d2 assert d1 is not d2 -@pytest.mark.parametrize( - "cls", - ["MultiDict", "CIMultiDict", "MultiDictProxy", "CIMultiDictProxy"], - indirect=True, -) -def test_subclassing(cls: Any) -> None: - class MyClass(cls): # type: ignore[valid-type,misc] +def test_multidict_subclassing( + multidict_class: Type[MutableMultiMapping[str]], +) -> None: + class DummyMultidict(multidict_class): # type: ignore[valid-type,misc] + pass + + +def test_multidict_proxy_subclassing( + any_multidict_proxy_class: Type[MultiMapping[str]], +) -> None: + class DummyMultidictProxy(any_multidict_proxy_class): # type: ignore[valid-type,misc] pass class BaseMultiDictTest: - def test_instantiate__empty(self, cls: _MultiDictClasses) -> None: + def test_instantiate__empty(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls() empty: Mapping[str, str] = {} assert d == empty @@ -126,12 +129,12 @@ def test_instantiate__empty(self, cls: _MultiDictClasses) -> None: assert cls() != list() # type: ignore[comparison-overlap] with pytest.raises(TypeError, match=r"(2 given)"): - cls(("key1", "value1"), ("key2", "value2")) # type: ignore[arg-type,call-arg] # noqa: E501 + cls(("key1", "value1"), ("key2", "value2")) # type: ignore[call-arg] # noqa: E501 @pytest.mark.parametrize("arg0", [[("key", "value1")], {"key": "value1"}]) def test_instantiate__from_arg0( self, - cls: _MultiDictClasses, + cls: Type[MutableMultiMapping[str]], arg0: Union[List[Tuple[str, str]], Dict[str, str]], ) -> None: d = cls(arg0) @@ -142,7 +145,7 @@ def test_instantiate__from_arg0( assert list(d.values()) == ["value1"] assert list(d.items()) == [("key", "value1")] - def test_instantiate__with_kwargs(self, cls: _MultiDictClasses) -> None: + def test_instantiate__with_kwargs(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")], key2="value2") assert d == {"key": "value1", "key2": "value2"} @@ -162,13 +165,13 @@ def test_instantiate__from_generator( assert sorted(d.values()) == [0, 1] assert sorted(d.items()) == [("0", 0), ("1", 1)] - def test_instantiate__from_list_of_lists(self, cls: _MultiDictClasses) -> None: + def test_instantiate__from_list_of_lists(self, cls: Type[MutableMultiMapping[str]]) -> None: # Should work at runtime, but won't type check. - d = cls([["key", "value1"]]) # type: ignore[list-item] + d = cls([["key", "value1"]]) # type: ignore[call-arg] assert d == {"key": "value1"} def test_instantiate__from_list_of_custom_pairs( - self, cls: _MultiDictClasses + self, cls: Type[MutableMultiMapping[str]], ) -> None: class Pair: def __len__(self) -> int: @@ -183,10 +186,10 @@ def __getitem__(self, pos: int) -> str: raise IndexError # Works at runtime, but won't type check. - d = cls([Pair()]) # type: ignore[list-item] + d = cls([Pair()]) assert d == {"key": "value1"} - def test_getone(self, cls: _MultiDictClasses) -> None: + def test_getone(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")], key="value2") assert d.getone("key") == "value1" @@ -248,58 +251,58 @@ def test_items__contains( assert ("foo", "bar") not in d.items() - def test_cannot_create_from_unaccepted(self, cls: _MultiDictClasses) -> None: + def test_cannot_create_from_unaccepted(self, cls: Type[MutableMultiMapping[str]]) -> None: with pytest.raises(TypeError): - cls([(1, 2, 3)]) # type: ignore[list-item] + cls([(1, 2, 3)]) # type: ignore[call-arg] - def test_keys_is_set_less(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_less(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() < {"key", "key2"} - def test_keys_is_set_less_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_less_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() <= {"key"} - def test_keys_is_set_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() == {"key"} - def test_keys_is_set_greater(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_greater(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} > d.keys() - def test_keys_is_set_greater_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_greater_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key"} >= d.keys() - def test_keys_is_set_not_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_not_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() != {"key2"} - def test_eq(self, cls: _MultiDictClasses) -> None: + def test_eq(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key": "value1"} == d - def test_eq2(self, cls: _MultiDictClasses) -> None: + def test_eq2(self, cls: Type[MutableMultiMapping[str]]) -> None: d1 = cls([("key", "value1")]) d2 = cls([("key2", "value1")]) assert d1 != d2 - def test_eq3(self, cls: _MultiDictClasses) -> None: + def test_eq3(self, cls: Type[MutableMultiMapping[str]]) -> None: d1 = cls([("key", "value1")]) d2 = cls() assert d1 != d2 - def test_eq_other_mapping_contains_more_keys(self, cls: _MultiDictClasses) -> None: + def test_eq_other_mapping_contains_more_keys(self, cls: Type[MutableMultiMapping[str]]) -> None: d1 = cls(foo="bar") d2 = dict(foo="bar", bar="baz") @@ -342,60 +345,60 @@ def __len__(self) -> int: with pytest.raises(ZeroDivisionError): d1 == d2 - def test_ne(self, cls: _MultiDictClasses) -> None: + def test_ne(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d != {"key": "another_value"} - def test_and(self, cls: _MultiDictClasses) -> None: + def test_and(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key"} == d.keys() & {"key", "key2"} - def test_and2(self, cls: _MultiDictClasses) -> None: + def test_and2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key"} == {"key", "key2"} & d.keys() - def test_or(self, cls: _MultiDictClasses) -> None: + def test_or(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} == d.keys() | {"key2"} - def test_or2(self, cls: _MultiDictClasses) -> None: + def test_or2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} == {"key2"} | d.keys() - def test_sub(self, cls: _MultiDictClasses) -> None: + def test_sub(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key"} == d.keys() - {"key2"} - def test_sub2(self, cls: _MultiDictClasses) -> None: + def test_sub2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key3"} == {"key", "key2", "key3"} - d.keys() - def test_xor(self, cls: _MultiDictClasses) -> None: + def test_xor(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key", "key3"} == d.keys() ^ {"key2", "key3"} - def test_xor2(self, cls: _MultiDictClasses) -> None: + def test_xor2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key", "key3"} == {"key2", "key3"} ^ d.keys() @pytest.mark.parametrize("_set, expected", [({"key2"}, True), ({"key"}, False)]) def test_isdisjoint( - self, cls: _MultiDictClasses, _set: Set[str], expected: bool + self, cls: Type[MutableMultiMapping[str]], _set: Set[str], expected: bool ) -> None: d = cls([("key", "value1")]) assert d.keys().isdisjoint(_set) == expected - def test_repr_issue_410(self, cls: _MultiDictClasses) -> None: + def test_repr_issue_410(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls() try: @@ -412,7 +415,7 @@ def test_repr_issue_410(self, cls: _MultiDictClasses) -> None: @pytest.mark.parametrize("other", [{"other"}]) def test_op_issue_410( self, - cls: _MultiDictClasses, + cls: Type[MutableMultiMapping[str]], op: Callable[[object, object], object], other: Set[str], ) -> None: @@ -426,7 +429,7 @@ def test_op_issue_410( assert sys.exc_info()[1] == e - def test_weakref(self, cls: _MultiDictClasses) -> None: + def test_weakref(self, cls: Type[MutableMultiMapping[str]]) -> None: called = False def cb(wr: object) -> None: @@ -493,9 +496,21 @@ def test_ctor_deque_arg_and_kwds( class TestMultiDict(BaseMultiDictTest): - @pytest.fixture(params=["MultiDict", ("MultiDict", "MultiDictProxy")]) - def cls(self, request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + @pytest.fixture( + params=[ + ("MultiDict", ), + ("MultiDict", "MultiDictProxy"), + ], + ) + def cls( # type: ignore[misc] + self, + request: pytest.FixtureRequest, + multidict_module: ModuleType, + ) -> Callable[ + ..., + MultiMapping[int | str] | MutableMultiMapping[int | str], + ]: + return chained_callable(multidict_module, request.param) def test__repr__(self, cls: Type[MultiDict[str]]) -> None: d = cls() @@ -548,9 +563,21 @@ def test_values__repr__(self, cls: Type[MultiDict[str]]) -> None: class TestCIMultiDict(BaseMultiDictTest): - @pytest.fixture(params=["CIMultiDict", ("CIMultiDict", "CIMultiDictProxy")]) - def cls(self, request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + @pytest.fixture( + params=[ + ("CIMultiDict", ), + ("CIMultiDict", "CIMultiDictProxy"), + ], + ) + def cls( # type: ignore[misc] + self, + request: pytest.FixtureRequest, + multidict_module: ModuleType, + ) -> Callable[ + ..., + MultiMapping[int | str] | MutableMultiMapping[int | str], + ]: + return chained_callable(multidict_module, request.param) def test_basics(self, cls: Type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], KEY="value2") diff --git a/tests/test_mutable_multidict.py b/tests/test_mutable_multidict.py index 3d4d16ac0..d06698e16 100644 --- a/tests/test_mutable_multidict.py +++ b/tests/test_mutable_multidict.py @@ -1,40 +1,40 @@ import string import sys +from typing import Type import pytest +from multidict import MultiMapping, MutableMultiMapping -class TestMutableMultiDict: - @pytest.fixture - def cls(self, _multidict): - return _multidict.MultiDict - - @pytest.fixture - def proxy_cls(self, _multidict): - return _multidict.MultiDictProxy - - @pytest.fixture - def istr(self, _multidict): - return _multidict.istr - def test_copy(self, cls): - d1 = cls(key="value", a="b") +class TestMutableMultiDict: + def test_copy( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_sensitive_multidict_class(key="value", a="b") d2 = d1.copy() assert d1 == d2 assert d1 is not d2 - def test__repr__(self, cls): - d = cls() - assert str(d) == "<%s()>" % cls.__name__ + def test__repr__( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() + assert str(d) == "<%s()>" % case_sensitive_multidict_class.__name__ - d = cls([("key", "one"), ("key", "two")]) + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")]) - expected = "<%s('key': 'one', 'key': 'two')>" % cls.__name__ + expected = "<%s('key': 'one', 'key': 'two')>" % case_sensitive_multidict_class.__name__ assert str(d) == expected - def test_getall(self, cls): - d = cls([("key", "value1")], key="value2") + def test_getall( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "value1")], key="value2") assert len(d) == 2 assert d.getall("key") == ["value1", "value2"] @@ -45,8 +45,11 @@ def test_getall(self, cls): default = object() assert d.getall("some_key", default) is default - def test_add(self, cls): - d = cls() + def test_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert d == {} d["key"] = "one" @@ -65,8 +68,11 @@ def test_add(self, cls): assert 3 == len(d) assert d.getall("foo") == ["bar"] - def test_extend(self, cls): - d = cls() + def test_extend( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert d == {} d.extend([("key", "one"), ("key", "two")], key=3, foo="bar") @@ -79,7 +85,7 @@ def test_extend(self, cls): assert ("key", 3) in itms assert ("foo", "bar") in itms - other = cls(bar="baz") + other = case_sensitive_multidict_class(bar="baz") assert other == {"bar": "baz"} d.extend(other) @@ -94,24 +100,34 @@ def test_extend(self, cls): with pytest.raises(TypeError): d.extend("foo", "bar") - def test_extend_from_proxy(self, cls, proxy_cls): - d = cls([("a", "a"), ("b", "b")]) - proxy = proxy_cls(d) + def test_extend_from_proxy( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_sensitive_multidict_proxy_class: Type[MultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("a", "a"), ("b", "b")]) + proxy = case_sensitive_multidict_proxy_class(d) - d2 = cls() + d2 = case_sensitive_multidict_class() d2.extend(proxy) assert [("a", "a"), ("b", "b")] == list(d2.items()) - def test_clear(self, cls): - d = cls([("key", "one")], key="two", foo="bar") + def test_clear( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one")], key="two", foo="bar") d.clear() assert d == {} assert list(d.items()) == [] - def test_del(self, cls): - d = cls([("key", "one"), ("key", "two")], foo="bar") + def test_del( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert list(d.keys()) == ["key", "key", "foo"] del d["key"] @@ -121,37 +137,52 @@ def test_del(self, cls): with pytest.raises(KeyError, match="key"): del d["key"] - def test_set_default(self, cls): - d = cls([("key", "one"), ("key", "two")], foo="bar") + def test_set_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert "one" == d.setdefault("key", "three") assert "three" == d.setdefault("otherkey", "three") assert "otherkey" in d assert "three" == d["otherkey"] - def test_popitem(self, cls): - d = cls() + def test_popitem( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") assert ("key", "val1") == d.popitem() assert [("key", "val2")] == list(d.items()) - def test_popitem_empty_multidict(self, cls): - d = cls() + def test_popitem_empty_multidict( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(KeyError): d.popitem() - def test_pop(self, cls): - d = cls() + def test_pop( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") assert "val1" == d.pop("key") assert {"key": "val2"} == d - def test_pop2(self, cls): - d = cls() + def test_pop2( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key2", "val2") d.add("key", "val3") @@ -159,22 +190,31 @@ def test_pop2(self, cls): assert "val1" == d.pop("key") assert [("key2", "val2"), ("key", "val3")] == list(d.items()) - def test_pop_default(self, cls): - d = cls(other="val") + def test_pop_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class(other="val") assert "default" == d.pop("key", "default") assert "other" in d - def test_pop_raises(self, cls): - d = cls(other="val") + def test_pop_raises( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class(other="val") with pytest.raises(KeyError, match="key"): d.pop("key") assert "other" in d - def test_replacement_order(self, cls): - d = cls() + def test_replacement_order( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key1", "val1") d.add("key2", "val2") d.add("key1", "val3") @@ -186,39 +226,59 @@ def test_replacement_order(self, cls): assert expected == list(d.items()) - def test_nonstr_key(self, cls): - d = cls() + def test_nonstr_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(TypeError): d[1] = "val" - def test_istr_key(self, cls, istr): - d = cls() - d[istr("1")] = "val" - assert type(list(d.keys())[0]) is istr - - def test_str_derived_key(self, cls): + def test_istr_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_sensitive_multidict_class() + d[case_insensitive_str_class("1")] = "val" + assert type(list(d.keys())[0]) is case_insensitive_str_class + + def test_str_derived_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: class A(str): pass - d = cls() + d = case_sensitive_multidict_class() d[A("1")] = "val" assert type(list(d.keys())[0]) is A - def test_istr_key_add(self, cls, istr): - d = cls() - d.add(istr("1"), "val") - assert type(list(d.keys())[0]) is istr - - def test_str_derived_key_add(self, cls): + def test_istr_key_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_sensitive_multidict_class() + d.add(case_insensitive_str_class("1"), "val") + assert type(list(d.keys())[0]) is case_insensitive_str_class + + def test_str_derived_key_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: class A(str): pass - d = cls() + d = case_sensitive_multidict_class() d.add(A("1"), "val") assert type(list(d.keys())[0]) is A - def test_popall(self, cls): - d = cls() + def test_popall( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key1", "val1") d.add("key2", "val2") d.add("key1", "val3") @@ -226,18 +286,27 @@ def test_popall(self, cls): assert ["val1", "val3"] == ret assert {"key2": "val2"} == d - def test_popall_default(self, cls): - d = cls() + def test_popall_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert "val" == d.popall("key", "val") - def test_popall_key_error(self, cls): - d = cls() + def test_popall_key_error( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(KeyError, match="key"): d.popall("key") - def test_large_multidict_resizing(self, cls): + def test_large_multidict_resizing( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: SIZE = 1024 - d = cls() + d = case_sensitive_multidict_class() for i in range(SIZE): d["key" + str(i)] = i @@ -248,20 +317,11 @@ def test_large_multidict_resizing(self, cls): class TestCIMutableMultiDict: - @pytest.fixture - def cls(self, _multidict): - return _multidict.CIMultiDict - - @pytest.fixture - def proxy_cls(self, _multidict): - return _multidict.CIMultiDictProxy - - @pytest.fixture - def istr(self, _multidict): - return _multidict.istr - - def test_getall(self, cls): - d = cls([("KEY", "value1")], KEY="value2") + def test_getall( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "value1")], KEY="value2") assert d != {"KEY": "value1"} assert len(d) == 2 @@ -271,53 +331,70 @@ def test_getall(self, cls): with pytest.raises(KeyError, match="some_key"): d.getall("some_key") - def test_ctor(self, cls): - d = cls(k1="v1") + def test_ctor( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(k1="v1") assert "v1" == d["K1"] assert ("k1", "v1") in d.items() - def test_setitem(self, cls): - d = cls() + def test_setitem( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d["k1"] = "v1" assert "v1" == d["K1"] assert ("k1", "v1") in d.items() - def test_delitem(self, cls): - d = cls() + def test_delitem( + self, case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d["k1"] = "v1" assert "K1" in d del d["k1"] assert "K1" not in d - def test_copy(self, cls): - d1 = cls(key="KEY", a="b") + def test_copy( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_insensitive_multidict_class(key="KEY", a="b") d2 = d1.copy() assert d1 == d2 assert d1.items() == d2.items() assert d1 is not d2 - def test__repr__(self, cls): - d = cls() - assert str(d) == "<%s()>" % cls.__name__ + def test__repr__( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() + assert str(d) == "<%s()>" % case_insensitive_multidict_class.__name__ - d = cls([("KEY", "one"), ("KEY", "two")]) + d = case_insensitive_multidict_class([("KEY", "one"), ("KEY", "two")]) - expected = "<%s('KEY': 'one', 'KEY': 'two')>" % cls.__name__ + expected = "<%s('KEY': 'one', 'KEY': 'two')>" % case_insensitive_multidict_class.__name__ assert str(d) == expected - def test_add(self, cls): - d = cls() + def test_add( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() assert d == {} d["KEY"] = "one" assert ("KEY", "one") in d.items() - assert d == cls({"Key": "one"}) + assert d == case_insensitive_multidict_class({"Key": "one"}) assert d.getall("key") == ["one"] d["KEY"] = "two" assert ("KEY", "two") in d.items() - assert d == cls({"Key": "two"}) + assert d == case_insensitive_multidict_class({"Key": "two"}) assert d.getall("key") == ["two"] d.add("KEY", "one") @@ -335,8 +412,11 @@ def test_add(self, cls): assert 4 == len(d) assert d.getall("test") == ["test"] - def test_extend(self, cls): - d = cls() + def test_extend( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() assert d == {} d.extend([("KEY", "one"), ("key", "two")], key=3, foo="bar") @@ -348,7 +428,7 @@ def test_extend(self, cls): assert ("key", 3) in itms assert ("foo", "bar") in itms - other = cls(Bar="baz") + other = case_insensitive_multidict_class(Bar="baz") assert other == {"Bar": "baz"} d.extend(other) @@ -365,24 +445,34 @@ def test_extend(self, cls): with pytest.raises(TypeError): d.extend("foo", "bar") - def test_extend_from_proxy(self, cls, proxy_cls): - d = cls([("a", "a"), ("b", "b")]) - proxy = proxy_cls(d) + def test_extend_from_proxy( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_multidict_proxy_class: Type[MultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("a", "a"), ("b", "b")]) + proxy = case_insensitive_multidict_proxy_class(d) - d2 = cls() + d2 = case_insensitive_multidict_class() d2.extend(proxy) assert [("a", "a"), ("b", "b")] == list(d2.items()) - def test_clear(self, cls): - d = cls([("KEY", "one")], key="two", foo="bar") + def test_clear( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "one")], key="two", foo="bar") d.clear() assert d == {} assert list(d.items()) == [] - def test_del(self, cls): - d = cls([("KEY", "one"), ("key", "two")], foo="bar") + def test_del( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "one"), ("key", "two")], foo="bar") del d["key"] assert d == {"foo": "bar"} @@ -391,16 +481,22 @@ def test_del(self, cls): with pytest.raises(KeyError, match="key"): del d["key"] - def test_set_default(self, cls): - d = cls([("KEY", "one"), ("key", "two")], foo="bar") + def test_set_default( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "one"), ("key", "two")], foo="bar") assert "one" == d.setdefault("key", "three") assert "three" == d.setdefault("otherkey", "three") assert "otherkey" in d assert ("otherkey", "three") in d.items() assert "three" == d["OTHERKEY"] - def test_popitem(self, cls): - d = cls() + def test_popitem( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") @@ -409,57 +505,83 @@ def test_popitem(self, cls): assert isinstance(pair[0], str) assert [("key", "val2")] == list(d.items()) - def test_popitem_empty_multidict(self, cls): - d = cls() + def test_popitem_empty_multidict( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() with pytest.raises(KeyError): d.popitem() - def test_pop(self, cls): - d = cls() + def test_pop( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") assert "val1" == d.pop("KEY") assert {"key": "val2"} == d - def test_pop_lowercase(self, cls): - d = cls() + def test_pop_lowercase( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") assert "val1" == d.pop("key") assert {"key": "val2"} == d - def test_pop_default(self, cls): - d = cls(OTHER="val") + def test_pop_default( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(OTHER="val") assert "default" == d.pop("key", "default") assert "other" in d - def test_pop_raises(self, cls): - d = cls(OTHER="val") + def test_pop_raises( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(OTHER="val") with pytest.raises(KeyError, match="KEY"): d.pop("KEY") assert "other" in d - def test_extend_with_istr(self, cls, istr): - us = istr("aBc") - d = cls() + def test_extend_with_istr( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + us = case_insensitive_str_class("aBc") + d = case_insensitive_multidict_class() d.extend([(us, "val")]) assert [("aBc", "val")] == list(d.items()) - def test_copy_istr(self, cls, istr): - d = cls({istr("Foo"): "bar"}) + def test_copy_istr( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_insensitive_multidict_class({case_insensitive_str_class("Foo"): "bar"}) d2 = d.copy() assert d == d2 - def test_eq(self, cls): - d1 = cls(Key="val") - d2 = cls(KEY="val") + def test_eq( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_insensitive_multidict_class(Key="val") + d2 = case_insensitive_multidict_class(KEY="val") assert d1 == d2 @@ -467,8 +589,11 @@ def test_eq(self, cls): sys.implementation.name == "pypy", reason="getsizeof() is not implemented on PyPy", ) - def test_sizeof(self, cls): - md = cls() + def test_sizeof( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + md = case_insensitive_multidict_class() s1 = sys.getsizeof(md) for i in string.ascii_lowercase: for j in string.ascii_uppercase: @@ -481,29 +606,41 @@ def test_sizeof(self, cls): sys.implementation.name == "pypy", reason="getsizeof() is not implemented on PyPy", ) - def test_min_sizeof(self, cls): - md = cls() + def test_min_sizeof( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + md = case_insensitive_multidict_class() assert sys.getsizeof(md) < 1024 - def test_issue_620_items(self, cls): + def test_issue_620_items( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_items = d.items() d["c"] = "000" # This causes an error on pypy. list(before_mutation_items) - def test_issue_620_keys(self, cls): + def test_issue_620_keys( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_keys = d.keys() d["c"] = "000" # This causes an error on pypy. list(before_mutation_keys) - def test_issue_620_values(self, cls): + def test_issue_620_values( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_values = d.values() d["c"] = "000" # This causes an error on pypy. diff --git a/tests/test_pickle.py b/tests/test_pickle.py index 3a85956e9..186102da4 100644 --- a/tests/test_pickle.py +++ b/tests/test_pickle.py @@ -3,78 +3,35 @@ import pytest -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_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - here = Path(__file__).resolve().parent -@pytest.fixture( - params=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"] -) -def cls_name(request): - return request.param - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param - - -def test_pickle(cls, pickle_protocol): - d = cls([("a", 1), ("a", 2)]) +def test_pickle(multidict_class, pickle_protocol): + d = multidict_class([("a", 1), ("a", 2)]) pbytes = pickle.dumps(d, pickle_protocol) obj = pickle.loads(pbytes) assert d == obj - assert isinstance(obj, cls) + assert isinstance(obj, multidict_class) -def test_pickle_proxy(proxy_classes): - proxy_cls, dict_cls = proxy_classes - d = dict_cls([("a", 1), ("a", 2)]) - proxy = proxy_cls(d) +def test_pickle_proxy(multidict_class, any_multidict_proxy_class): + d = multidict_class([("a", 1), ("a", 2)]) + proxy = any_multidict_proxy_class(d) with pytest.raises(TypeError): pickle.dumps(proxy) -def test_load_from_file(pickle_protocol, cls_name): - cls = globals()[cls_name] - d = cls([("a", 1), ("a", 2)]) - fname = "{}.pickle.{}".format(cls_name.lower(), pickle_protocol) +def test_load_from_file(multidict_class, multidict_implementation, pickle_protocol): + multidict_class_name = multidict_class.__name__ + pickle_file_basename = "-".join(( + multidict_class_name.lower(), + multidict_implementation.tag, + )) + d = multidict_class([("a", 1), ("a", 2)]) + fname = f"{pickle_file_basename}.pickle.{pickle_protocol}" p = here / fname with p.open("rb") as f: obj = pickle.load(f) assert d == obj - assert isinstance(obj, cls) + assert isinstance(obj, multidict_class) diff --git a/tests/test_types.py b/tests/test_types.py index 3ae2cbb84..870ac3f08 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -4,62 +4,62 @@ import pytest -def test_proxies(_multidict): - assert issubclass(_multidict.CIMultiDictProxy, _multidict.MultiDictProxy) +def test_proxies(multidict_module): + assert issubclass(multidict_module.CIMultiDictProxy, multidict_module.MultiDictProxy) -def test_dicts(_multidict): - assert issubclass(_multidict.CIMultiDict, _multidict.MultiDict) +def test_dicts(multidict_module): + assert issubclass(multidict_module.CIMultiDict, multidict_module.MultiDict) -def test_proxy_not_inherited_from_dict(_multidict): - assert not issubclass(_multidict.MultiDictProxy, _multidict.MultiDict) +def test_proxy_not_inherited_from_dict(multidict_module): + assert not issubclass(multidict_module.MultiDictProxy, multidict_module.MultiDict) -def test_dict_not_inherited_from_proxy(_multidict): - assert not issubclass(_multidict.MultiDict, _multidict.MultiDictProxy) +def test_dict_not_inherited_from_proxy(multidict_module): + assert not issubclass(multidict_module.MultiDict, multidict_module.MultiDictProxy) -def test_multidict_proxy_copy_type(_multidict): - d = _multidict.MultiDict(key="val") - p = _multidict.MultiDictProxy(d) - assert isinstance(p.copy(), _multidict.MultiDict) +def test_multidict_proxy_copy_type(multidict_module): + d = multidict_module.MultiDict(key="val") + p = multidict_module.MultiDictProxy(d) + assert isinstance(p.copy(), multidict_module.MultiDict) -def test_cimultidict_proxy_copy_type(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.CIMultiDictProxy(d) - assert isinstance(p.copy(), _multidict.CIMultiDict) +def test_cimultidict_proxy_copy_type(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.CIMultiDictProxy(d) + assert isinstance(p.copy(), multidict_module.CIMultiDict) -def test_create_multidict_proxy_from_nonmultidict(_multidict): +def test_create_multidict_proxy_from_nonmultidict(multidict_module): with pytest.raises(TypeError): - _multidict.MultiDictProxy({}) + multidict_module.MultiDictProxy({}) -def test_create_multidict_proxy_from_cimultidict(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.MultiDictProxy(d) +def test_create_multidict_proxy_from_cimultidict(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.MultiDictProxy(d) assert p == d -def test_create_multidict_proxy_from_multidict_proxy_from_mdict(_multidict): - d = _multidict.MultiDict(key="val") - p = _multidict.MultiDictProxy(d) +def test_create_multidict_proxy_from_multidict_proxy_from_mdict(multidict_module): + d = multidict_module.MultiDict(key="val") + p = multidict_module.MultiDictProxy(d) assert p == d - p2 = _multidict.MultiDictProxy(p) + p2 = multidict_module.MultiDictProxy(p) assert p2 == p -def test_create_cimultidict_proxy_from_cimultidict_proxy_from_ci(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.CIMultiDictProxy(d) +def test_create_cimultidict_proxy_from_cimultidict_proxy_from_ci(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.CIMultiDictProxy(d) assert p == d - p2 = _multidict.CIMultiDictProxy(p) + p2 = multidict_module.CIMultiDictProxy(p) assert p2 == p -def test_create_cimultidict_proxy_from_nonmultidict(_multidict): +def test_create_cimultidict_proxy_from_nonmultidict(multidict_module): with pytest.raises( TypeError, match=( @@ -67,11 +67,11 @@ def test_create_cimultidict_proxy_from_nonmultidict(_multidict): "not " ), ): - _multidict.CIMultiDictProxy({}) + multidict_module.CIMultiDictProxy({}) -def test_create_ci_multidict_proxy_from_multidict(_multidict): - d = _multidict.MultiDict(key="val") +def test_create_ci_multidict_proxy_from_multidict(multidict_module): + d = multidict_module.MultiDict(key="val") with pytest.raises( TypeError, match=( @@ -79,31 +79,30 @@ def test_create_ci_multidict_proxy_from_multidict(_multidict): "not " ), ): - _multidict.CIMultiDictProxy(d) + multidict_module.CIMultiDictProxy(d) @pytest.mark.skipif( sys.version_info >= (3, 9), reason="Python 3.9 uses GenericAlias which is different" ) -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 +def test_generic_exists(multidict_module) -> None: + assert multidict_module.MultiDict[int] is multidict_module.MultiDict + assert multidict_module.MultiDictProxy[int] is multidict_module.MultiDictProxy + assert multidict_module.CIMultiDict[int] is multidict_module.CIMultiDict + assert multidict_module.CIMultiDictProxy[int] is multidict_module.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,) +def test_generic_alias(multidict_module) -> None: + assert multidict_module.MultiDict[int] == types.GenericAlias(multidict_module.MultiDict, (int,)) + assert multidict_module.MultiDictProxy[int] == types.GenericAlias( + multidict_module.MultiDictProxy, (int,) ) - assert _multidict.CIMultiDict[int] == types.GenericAlias( - _multidict.CIMultiDict, (int,) + assert multidict_module.CIMultiDict[int] == types.GenericAlias( + multidict_module.CIMultiDict, (int,) ) - assert _multidict.CIMultiDictProxy[int] == types.GenericAlias( - _multidict.CIMultiDictProxy, (int,) + assert multidict_module.CIMultiDictProxy[int] == types.GenericAlias( + multidict_module.CIMultiDictProxy, (int,) ) diff --git a/tests/test_update.py b/tests/test_update.py index 4bacdbce7..10b15292a 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -1,97 +1,68 @@ from collections import deque +from typing import Type -import pytest +from multidict import MultiMapping -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_EXTENSIONS: - from multidict._multidict import CIMultiDict, MultiDict # type: ignore - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture -def md_cls(_multidict): - return _multidict.MultiDict - - -@pytest.fixture -def ci_md_cls(_multidict): - return _multidict.CIMultiDict - - -@pytest.fixture -def istr(_multidict): - return _multidict.istr - - -def test_update_replace(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4), ("b", 5), ("a", 6)]) +def test_update_replace(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = multidict_class([("a", 4), ("b", 5), ("a", 6)]) obj1.update(obj2) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_append(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4), ("a", 5), ("a", 6)]) +def test_update_append(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = multidict_class([("a", 4), ("a", 5), ("a", 6)]) obj1.update(obj2) expected = [("a", 4), ("b", 2), ("a", 5), ("c", 10), ("a", 6)] assert list(obj1.items()) == expected -def test_update_remove(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4)]) +def test_update_remove(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = multidict_class([("a", 4)]) obj1.update(obj2) expected = [("a", 4), ("b", 2), ("c", 10)] assert list(obj1.items()) == expected -def test_update_replace_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_replace_seq(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4), ("b", 5), ("a", 6)] obj1.update(obj2) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_replace_seq2(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_replace_seq2(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj1.update([("a", 4)], b=5, a=6) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_append_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_append_seq(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4), ("a", 5), ("a", 6)] obj1.update(obj2) expected = [("a", 4), ("b", 2), ("a", 5), ("c", 10), ("a", 6)] assert list(obj1.items()) == expected -def test_update_remove_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_remove_seq(multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4)] obj1.update(obj2) expected = [("a", 4), ("b", 2), ("c", 10)] assert list(obj1.items()) == expected -def test_update_md(md_cls): - d = md_cls() +def test_update_md( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") d.add("key2", "val3") @@ -101,19 +72,24 @@ def test_update_md(md_cls): assert [("key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_istr_ci_md(ci_md_cls, istr): - d = ci_md_cls() - d.add(istr("KEY"), "val1") +def test_update_istr_ci_md( + case_insensitive_multidict_class: Type[MultiMapping[str]], + case_insensitive_str_class: str, +) -> None: + d = case_insensitive_multidict_class() + d.add(case_insensitive_str_class("KEY"), "val1") d.add("key", "val2") d.add("key2", "val3") - d.update({istr("key"): "val"}) + d.update({case_insensitive_str_class("key"): "val"}) assert [("key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_ci_md(ci_md_cls): - d = ci_md_cls() +def test_update_ci_md( + case_insensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") d.add("key2", "val3") @@ -123,24 +99,30 @@ def test_update_ci_md(ci_md_cls): assert [("Key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_list_arg_and_kwds(cls): - obj = cls() +def test_update_list_arg_and_kwds( + multidict_class: Type[MultiMapping[str]], +) -> None: + obj = multidict_class() arg = [("a", 1)] obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] assert arg == [("a", 1)] -def test_update_tuple_arg_and_kwds(cls): - obj = cls() +def test_update_tuple_arg_and_kwds( + multidict_class: Type[MultiMapping[str]], +) -> None: + obj = multidict_class() arg = (("a", 1),) obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] assert arg == (("a", 1),) -def test_update_deque_arg_and_kwds(cls): - obj = cls() +def test_update_deque_arg_and_kwds( + multidict_class: Type[MultiMapping[str]], +) -> None: + obj = multidict_class() arg = deque([("a", 1)]) obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] diff --git a/tests/test_version.py b/tests/test_version.py index 067d6210c..6d36a3632 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,199 +1,222 @@ -from typing import Type +from typing import Callable, Type import pytest from multidict import MultiMapping -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_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - MultiDict, - getversion, - ) - - -class VersionMixin: - cls: Type[MultiMapping[str]] - - def getver(self, md): - raise NotImplementedError - - def test_getversion_bad_param(self): - with pytest.raises(TypeError): - self.getver(1) - - def test_ctor(self): - m1 = self.cls() - v1 = self.getver(m1) - m2 = self.cls() - v2 = self.getver(m2) - assert v1 != v2 - - def test_add(self): - m = self.cls() - v = self.getver(m) - m.add("key", "val") - assert self.getver(m) > v - - def test_delitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - del m["key"] - assert self.getver(m) > v - - def test_delitem_not_found(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - del m["notfound"] - assert self.getver(m) == v - - def test_setitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m["key"] = "val2" - assert self.getver(m) > v - - def test_setitem_not_found(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m["notfound"] = "val2" - assert self.getver(m) > v - - def test_clear(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.clear() - assert self.getver(m) > v - - def test_setdefault(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.setdefault("key2", "val2") - assert self.getver(m) > v - - def test_popone(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popone("key") - assert self.getver(m) > v - - def test_popone_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popone("key2", "default") - assert self.getver(m) == v - - def test_popone_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.popone("key2") - assert self.getver(m) == v - - def test_pop(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.pop("key") - assert self.getver(m) > v - - def test_pop_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.pop("key2", "default") - assert self.getver(m) == v - - def test_pop_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.pop("key2") - assert self.getver(m) == v - - def test_popall(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popall("key") - assert self.getver(m) > v - - def test_popall_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popall("key2", "default") - assert self.getver(m) == v - - def test_popall_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.popall("key2") - assert self.getver(m) == v - - def test_popitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popitem() - assert self.getver(m) > v - - def test_popitem_key_error(self): - m = self.cls() - v = self.getver(m) - with pytest.raises(KeyError): - m.popitem() - assert self.getver(m) == v - - -if USE_EXTENSIONS: - - class TestMultiDict(VersionMixin): - - cls = MultiDict - - def getver(self, md): - return getversion(md) - - -if USE_EXTENSIONS: - - class TestCIMultiDict(VersionMixin): - cls = CIMultiDict - def getver(self, md): - return getversion(md) - - -class TestPyMultiDict(VersionMixin): - - cls = _MultiDict # type: ignore[assignment] - - def getver(self, md): - return _getversion(md) - - -class TestPyCIMultiDict(VersionMixin): - - cls = _CIMultiDict # type: ignore[assignment] - - def getver(self, md): - return _getversion(md) +def test_getversion_bad_param(multidict_getversion_callable): + with pytest.raises(TypeError): + multidict_getversion_callable(1) + + +def test_ctor( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m1 = multidict_class() + v1 = multidict_getversion_callable(m1) + m2 = multidict_class() + v2 = multidict_getversion_callable(m2) + assert v1 != v2 + + +def test_add( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + v = multidict_getversion_callable(m) + m.add("key", "val") + assert multidict_getversion_callable(m) > v + + +def test_delitem( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + del m["key"] + assert multidict_getversion_callable(m) > v + + +def test_delitem_not_found( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + del m["notfound"] + assert multidict_getversion_callable(m) == v + + +def test_setitem( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m["key"] = "val2" + assert multidict_getversion_callable(m) > v + + +def test_setitem_not_found( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m["notfound"] = "val2" + assert multidict_getversion_callable(m) > v + + +def test_clear( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.clear() + assert multidict_getversion_callable(m) > v + + +def test_setdefault( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.setdefault("key2", "val2") + assert multidict_getversion_callable(m) > v + + +def test_popone( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popone("key") + assert multidict_getversion_callable(m) > v + + +def test_popone_default( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popone("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_popone_key_error( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popone("key2") + assert multidict_getversion_callable(m) == v + + +def test_pop( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.pop("key") + assert multidict_getversion_callable(m) > v + + +def test_pop_default( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.pop("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_pop_key_error( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.pop("key2") + assert multidict_getversion_callable(m) == v + + +def test_popall( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popall("key") + assert multidict_getversion_callable(m) > v + + +def test_popall_default( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popall("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_popall_key_error( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popall("key2") + assert multidict_getversion_callable(m) == v + + +def test_popitem( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popitem() + assert multidict_getversion_callable(m) > v + + +def test_popitem_key_error( + multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = multidict_class() + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popitem() + assert multidict_getversion_callable(m) == v