From 96a48f0c66ebe1ec2305c21390a3f6c059760af5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 30 Jul 2020 17:05:32 +0300 Subject: [PATCH] Stop using more-itertools We barely use it; the couple places that do are not really worth the extra dependency, I think the code is clearer without it. Also simplifies one (regular) itertools usage. Also improves a check and an error message in `pytest.raises`. --- changelog/7587.trivial.rst | 1 + setup.cfg | 1 - src/_pytest/fixtures.py | 7 +++---- src/_pytest/python_api.py | 22 ++++++++++------------ src/_pytest/terminal.py | 12 +++++++----- testing/python/raises.py | 19 +++++++++++++++++++ 6 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 changelog/7587.trivial.rst diff --git a/changelog/7587.trivial.rst b/changelog/7587.trivial.rst new file mode 100644 index 000000000..1477c97ca --- /dev/null +++ b/changelog/7587.trivial.rst @@ -0,0 +1 @@ +The dependency on the ``more-itertools`` package has been removed. diff --git a/setup.cfg b/setup.cfg index 31123f28e..518b6d41c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,6 @@ packages = install_requires = attrs>=17.4.0 iniconfig - more-itertools>=4.0.0 packaging pluggy>=0.12,<1.0 py>=1.8.2 diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 9521a7a17..d9f918745 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,6 +1,5 @@ import functools import inspect -import itertools import sys import warnings from collections import defaultdict @@ -1489,10 +1488,10 @@ class FixtureManager: else: argnames = () - usefixtures = itertools.chain.from_iterable( - mark.args for mark in node.iter_markers(name="usefixtures") + usefixtures = tuple( + arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args ) - initialnames = tuple(usefixtures) + argnames + initialnames = usefixtures + argnames fm = node.session._fixturemanager initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( initialnames, node, ignore_args=self._get_direct_parametrize_args(node) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index e30471995..fb6c76852 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,11 +1,9 @@ -import inspect import math import pprint from collections.abc import Iterable from collections.abc import Mapping from collections.abc import Sized from decimal import Decimal -from itertools import filterfalse from numbers import Number from types import TracebackType from typing import Any @@ -18,8 +16,6 @@ from typing import Tuple from typing import TypeVar from typing import Union -from more_itertools.more import always_iterable - import _pytest._code from _pytest.compat import overload from _pytest.compat import STRING_TYPES @@ -30,9 +26,6 @@ if TYPE_CHECKING: from typing import Type -BASE_TYPE = (type, STRING_TYPES) - - def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: at_str = " at {}".format(at) if at else "" return TypeError( @@ -680,11 +673,16 @@ def raises( # noqa: F811 documentation for :ref:`the try statement `. """ __tracebackhide__ = True - for exc in filterfalse( - inspect.isclass, always_iterable(expected_exception, BASE_TYPE) - ): - msg = "exceptions must be derived from BaseException, not %s" - raise TypeError(msg % type(exc)) + + if isinstance(expected_exception, type): + excepted_exceptions = (expected_exception,) # type: Tuple[Type[_E], ...] + else: + excepted_exceptions = expected_exception + for exc in excepted_exceptions: + if not isinstance(exc, type) or not issubclass(exc, BaseException): + msg = "expected exception must be a BaseException type, not {}" + not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ + raise TypeError(msg.format(not_a)) message = "DID NOT RAISE {}".format(expected_exception) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index ef9da50f3..cbca9ba46 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -25,7 +25,6 @@ from typing import Union import attr import pluggy import py -from more_itertools import collapse import pytest from _pytest import nodes @@ -715,11 +714,14 @@ class TerminalReporter: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: List[Union[str, List[str]]] + self, lines: Sequence[Union[str, Sequence[str]]] ) -> None: - lines.reverse() - for line in collapse(lines): - self.write_line(line) + for line_or_lines in reversed(lines): + if isinstance(line_or_lines, str): + self.write_line(line_or_lines) + else: + for line in line_or_lines: + self.write_line(line) def pytest_report_header(self, config: Config) -> List[str]: line = "rootdir: %s" % config.rootdir diff --git a/testing/python/raises.py b/testing/python/raises.py index 12d44495c..46b200921 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -283,3 +283,22 @@ class TestRaises: with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] pass assert "Unexpected keyword arguments" in str(excinfo.value) + + def test_expected_exception_is_not_a_baseexception(self) -> None: + with pytest.raises(TypeError) as excinfo: + with pytest.raises("hello"): # type: ignore[call-overload] + pass # pragma: no cover + assert "must be a BaseException type, not str" in str(excinfo.value) + + class NotAnException: + pass + + with pytest.raises(TypeError) as excinfo: + with pytest.raises(NotAnException): # type: ignore[type-var] + pass # pragma: no cover + assert "must be a BaseException type, not NotAnException" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + with pytest.raises(("hello", NotAnException)): # type: ignore[arg-type] + pass # pragma: no cover + assert "must be a BaseException type, not str" in str(excinfo.value)