diff --git a/pytest_cases/__init__.py b/pytest_cases/__init__.py index ed684d4f..d0c08db6 100644 --- a/pytest_cases/__init__.py +++ b/pytest_cases/__init__.py @@ -5,14 +5,10 @@ from .common_pytest_lazy_values import lazy_value, is_lazy from .common_others import unfold_expected_err, assert_exception, AUTO -AUTO2 = AUTO -"""Deprecated symbol, for retrocompatibility. Will be dropped soon.""" - from .fixture_core1_unions import fixture_union, NOT_USED, unpack_fixture, ignore_unused from .fixture_core2 import pytest_fixture_plus, fixture_plus, fixture, param_fixtures, param_fixture from .fixture_parametrize_plus import pytest_parametrize_plus, parametrize_plus, parametrize, fixture_ref - from .case_funcs import case, copy_case_info, set_case_id, get_case_id, get_case_marks, \ get_case_tags, matches_tag_query, is_case_class, is_case_function from .case_parametrizer_new import parametrize_with_cases, THIS_MODULE, get_all_cases, get_parametrize_args, \ @@ -29,6 +25,11 @@ from os import path as _path __version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir)) + +AUTO2 = AUTO +"""Deprecated symbol, for retrocompatibility. Will be dropped soon.""" + + __all__ = [ '__version__', # the submodules diff --git a/pytest_cases/case_funcs.py b/pytest_cases/case_funcs.py index eb871f6a..8a095a25 100644 --- a/pytest_cases/case_funcs.py +++ b/pytest_cases/case_funcs.py @@ -6,7 +6,7 @@ from decopatch import function_decorator, DECORATED try: # python 3.5+ - from typing import Type, Callable, Union, Optional, Any, Tuple, Dict, Iterable, List, Set + from typing import Callable, Union, Optional, Any, Tuple, Iterable, List, Set except ImportError: pass @@ -275,10 +275,16 @@ def matches_tag_query(case_fun, # type: Callable return selected +try: + SeveralMarkDecorators = Union[Tuple[MarkDecorator, ...], List[MarkDecorator], Set[MarkDecorator]] +except: # noqa + pass + + @function_decorator def case(id=None, # type: str # noqa tags=None, # type: Union[Any, Iterable[Any]] - marks=(), # type: Union[MarkDecorator, Tuple[MarkDecorator, ...], List[MarkDecorator], Set[MarkDecorator]] + marks=(), # type: Union[MarkDecorator, SeveralMarkDecorators] case_func=DECORATED # noqa ): """ diff --git a/pytest_cases/case_parametrizer_new.py b/pytest_cases/case_parametrizer_new.py index 0cf068cf..a5aba428 100644 --- a/pytest_cases/case_parametrizer_new.py +++ b/pytest_cases/case_parametrizer_new.py @@ -22,7 +22,7 @@ from .common_others import get_code_first_line, AUTO, qname, funcopy, needs_binding, get_function_host, \ in_same_module, get_host_module, get_class_that_defined_method from .common_pytest_marks import copy_pytest_marks, make_marked_parameter_value, remove_pytest_mark, filter_marks, \ - get_param_argnames_as_list + get_param_argnames_as_list, Mark from .common_pytest_lazy_values import LazyValue, LazyTuple, LazyTupleItem from .common_pytest import safe_isclass, MiniMetafunc, is_fixture, get_fixture_name, inject_host, add_fixture_params, \ list_all_fixtures_in, get_pytest_request_and_item, safe_isinstance @@ -269,8 +269,8 @@ def get_all_cases(parametrization_target, # type: Callable for c in cases: # load case or cases depending on type if safe_isclass(c): - # class - new_cases = extract_cases_from_class(c, case_fun_prefix=prefix, check_name=False) # do not check name, it was explicitly passed + # class - do not check name, it was explicitly passed + new_cases = extract_cases_from_class(c, case_fun_prefix=prefix, check_name=False) cases_funs += new_cases elif callable(c): # function @@ -303,7 +303,7 @@ def get_parametrize_args(host_class_or_module, # type: Union[Type, ModuleType import_fixtures=False, # type: bool debug=False # type: bool ): - # type: (...) -> List[Union[lazy_value, fixture_ref]] + # type: (...) -> List[CaseParamValue] """ Transforms a list of cases (obtained from `get_all_cases`) into a list of argvalues for `@parametrize`. Each case function `case_fun` is transformed into one or several `lazy_value`(s) or a `fixture_ref`: @@ -387,7 +387,7 @@ def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType] import_fixtures=False, # type: bool debug=False # type: bool ): - # type: (...) -> Tuple[lazy_value] + # type: (...) -> Tuple[CaseParamValue, ...] """Transform a single case into one or several `lazy_value`(s) or a `fixture_ref` to be used in `@parametrize` If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref` will @@ -464,7 +464,7 @@ def get_or_create_case_fixture(case_id, # type: str import_fixtures=False, # type: bool debug=False # type: bool ): - # type: (...) -> Tuple[str, Tuple[MarkInfo]] + # type: (...) -> Tuple[str, Tuple[Mark]] """ When case functions require fixtures, we want to rely on pytest to inject everything. Therefore we create a "case fixture" wrapping the case function. Since a case function may not be located in the same place @@ -529,8 +529,8 @@ def get_or_create_case_fixture(case_id, # type: str for f in list_all_fixtures_in(true_case_func_host, recurse_to_module=False, return_names=False): f_name = get_fixture_name(f) if (f_name in existing_fixture_names) or (f.__name__ in existing_fixture_names): - raise ValueError("Cannot import fixture %r from %r as it would override an existing symbol in " - "%r. Please set `@parametrize_with_cases(import_fixtures=False)`" + raise ValueError("Cannot import fixture %r from %r as it would override an existing symbol " + "in %r. Please set `@parametrize_with_cases(import_fixtures=False)`" "" % (f, from_module, target_host)) target_host_module = target_host if not target_in_class else get_host_module(target_host) setattr(target_host_module, f.__name__, f) @@ -786,7 +786,8 @@ def _of_interest(x): # noqa for m_name, m in getmembers(container, _of_interest): if is_case_class(m): co_firstlineno = get_code_first_line(m) - cls_cases = extract_cases_from_class(m, case_fun_prefix=case_fun_prefix, _case_param_factory=_case_param_factory) + cls_cases = extract_cases_from_class(m, case_fun_prefix=case_fun_prefix, + _case_param_factory=_case_param_factory) for _i, _m_item in enumerate(cls_cases): gen_line_nb = co_firstlineno + (_i / len(cls_cases)) cases_dct[gen_line_nb] = _m_item diff --git a/pytest_cases/common_mini_six.py b/pytest_cases/common_mini_six.py index 15c0c0bb..f2956d01 100644 --- a/pytest_cases/common_mini_six.py +++ b/pytest_cases/common_mini_six.py @@ -10,7 +10,7 @@ if PY3: string_types = str, else: - string_types = basestring, + string_types = basestring, # noqa # if PY3: @@ -49,17 +49,17 @@ # """) -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(type): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) +# def with_metaclass(meta, *bases): +# """Create a base class with a metaclass.""" +# # This requires a bit of explanation: the basic idea is to make a dummy +# # metaclass for one level of class instantiation that replaces itself with +# # the actual metaclass. +# class metaclass(type): +# +# def __new__(cls, name, this_bases, d): +# return meta(name, bases, d) +# +# @classmethod +# def __prepare__(cls, name, this_bases): +# return meta.__prepare__(name, bases) +# return type.__new__(metaclass, 'temporary_class', (), {}) diff --git a/pytest_cases/common_others.py b/pytest_cases/common_others.py index b286c88f..8bb87854 100644 --- a/pytest_cases/common_others.py +++ b/pytest_cases/common_others.py @@ -38,7 +38,7 @@ def get_code_first_line(f): raise ValueError("Cannot get code information for function or class %r" % f) -# Below is the beginning of a switch from our code scanning tool above to the same one than pytest. See `case_parametrizer_new` +# Below is the beginning of a switch from our scanning code to the same one than pytest. See `case_parametrizer_new` # from _pytest.compat import get_real_func as compat_get_real_func # # try: @@ -49,13 +49,19 @@ def get_code_first_line(f): try: ExpectedError = Optional[Union[Type[Exception], str, Exception, Callable[[Exception], Optional[bool]]]] """The expected error in case failure is expected. An exception type, instance, or a validation function""" + + ExpectedErrorType = Optional[Type[BaseException]] + ExpectedErrorPattern = Optional[re.Pattern] + ExpectedErrorInstance = Optional[BaseException] + ExpectedErrorValidator = Optional[Callable[[BaseException], Optional[bool]]] + except: # noqa pass def unfold_expected_err(expected_e # type: ExpectedError ): - # type: (...) -> Tuple[Optional[Type[BaseException]], Optional[re.Pattern], Optional[BaseException], Optional[Callable[[BaseException], Optional[bool]]]] + # type: (...) -> Tuple[ExpectedErrorType, ExpectedErrorPattern, ExpectedErrorInstance, ExpectedErrorValidator] """ 'Unfolds' the expected error `expected_e` to return a tuple of - expected error type @@ -132,7 +138,7 @@ class MyErr(ValueError): raise TypeError() # good repr pattern - ok - with assert_exception(r"ValueError\('hello'[,]+\)"): + with assert_exception(r"ValueError\\('hello'[,]+\\)"): raise ValueError("hello") # good instance equality check - ok diff --git a/pytest_cases/common_pytest.py b/pytest_cases/common_pytest.py index c8166eed..543c9063 100644 --- a/pytest_cases/common_pytest.py +++ b/pytest_cases/common_pytest.py @@ -16,11 +16,10 @@ except ImportError: from funcsigs import signature, Parameter # noqa -from distutils.version import LooseVersion from inspect import isgeneratorfunction, isclass try: - from typing import Union, Callable, Any, Optional, Tuple, Type # noqa + from typing import Union, Callable, Any, Optional, Tuple, Type, Iterable, Sized, List # noqa except ImportError: pass diff --git a/pytest_cases/common_pytest_lazy_values.py b/pytest_cases/common_pytest_lazy_values.py index 710bbe71..89303bc2 100644 --- a/pytest_cases/common_pytest_lazy_values.py +++ b/pytest_cases/common_pytest_lazy_values.py @@ -55,7 +55,7 @@ def __eq__(self, other): """Default equality method based on the _field_names""" try: return all(getattr(self, k) == getattr(other, k) for k in self._field_names) - except: + except Exception: # noqa return False def __repr__(self): @@ -85,7 +85,7 @@ def _unwrap(obj): Note: maybe from inspect import unwrap could do the same? """ start_obj = obj - for i in range(100): + for _ in range(100): # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function # to trigger a warning if it gets called directly instead of by pytest: we don't # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774) @@ -303,9 +303,16 @@ def __hash__(self): def __repr__(self): """Override the inherited method to avoid infinite recursion""" + + # lazy value tuple or cached tuple + if self.host.has_cached_value(raise_if_no_context=False): + tuple_to_represent = self.host.cached_value + else: + tuple_to_represent = self.host._lazyvalue # noqa + vals_to_display = ( ('item', self.item), # item number first for easier debug - ('tuple', self.host.cached_value if self.host.has_cached_value(raise_if_no_context=False) else self.host._lazyvalue), # lazy value tuple or cached tuple + ('tuple', tuple_to_represent), ) return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%r" % (k, v) for k, v in vals_to_display)) @@ -506,7 +513,7 @@ def is_lazy_value(argval): try: # note: we use the private and not public class here on purpose return isinstance(argval, _LazyValue) - except: + except Exception: # noqa return False @@ -519,7 +526,7 @@ def is_lazy(argval): try: # note: we use the private and not public classes here on purpose return isinstance(argval, (_LazyValue, LazyTuple, _LazyTupleItem)) - except: + except Exception: # noqa return False diff --git a/pytest_cases/filters.py b/pytest_cases/filters.py index abddce1c..7131c9b1 100644 --- a/pytest_cases/filters.py +++ b/pytest_cases/filters.py @@ -1,3 +1,7 @@ +# Authors: Sylvain MARIE +# + All contributors to +# +# License: 3-clause BSD, import re from .case_funcs import get_case_id, get_case_tags diff --git a/pytest_cases/fixture_core2.py b/pytest_cases/fixture_core2.py index 0c564b83..e24594fc 100644 --- a/pytest_cases/fixture_core2.py +++ b/pytest_cases/fixture_core2.py @@ -230,7 +230,8 @@ def _root_fixture(**_kwargs): def _create_fixture(_param_idx): if debug: - print("Creating nonparametrized 'view' fixture %r returning %r[%s]" % (argname, root_fixture_name, _param_idx)) + print("Creating nonparametrized 'view' fixture %r returning %r[%s]" + % (argname, root_fixture_name, _param_idx)) @fixture(name=argname, scope=scope, autouse=autouse, hook=hook, **kwargs) @with_signature("%s(%s)" % (argname, root_fixture_name)) @@ -338,7 +339,7 @@ class CombinedFixtureParamValue(object): __slots__ = 'param_defs', 'argvalues', def __init__(self, - param_defs, # type: Iterable[FixtureParam] + param_defs, # type: Iterable[FixtureParam] argvalues): self.param_defs = param_defs self.argvalues = argvalues diff --git a/pytest_cases/fixture_parametrize_plus.py b/pytest_cases/fixture_parametrize_plus.py index df19b84b..02acaf84 100644 --- a/pytest_cases/fixture_parametrize_plus.py +++ b/pytest_cases/fixture_parametrize_plus.py @@ -15,10 +15,10 @@ from collections.abc import Iterable except ImportError: # noqa from collections import Iterable - -try: - from typing import Union, Callable, List, Any, Sequence, Optional # noqa +try: + from typing import Union, Callable, List, Any, Sequence, Optional, Type # noqa + from types import ModuleType # noqa except ImportError: pass @@ -643,10 +643,11 @@ def parametrize(argnames=None, # type: str (3) new possibilities in argvalues: - one can include references to fixtures with `fixture_ref()` where can be the fixture name or - fixture function. When such a fixture reference is detected in the argvalues, a new function-scope "union" fixture - will be created with a unique name, and the test function will be wrapped so as to be injected with the correct - parameters from this fixture. Special test ids will be created to illustrate the switching between the various - normal parameters and fixtures. You can see debug print messages about all fixtures created using `debug=True` + fixture function. When such a fixture reference is detected in the argvalues, a new function-scope "union" + fixture will be created with a unique name, and the test function will be wrapped so as to be injected with the + correct parameters from this fixture. Special test ids will be created to illustrate the switching between the + various normal parameters and fixtures. You can see debug print messages about all fixtures created using + `debug=True` - one can include lazy argvalues with `lazy_value(, [id=..., marks=...])`. A `lazy_value` is the same thing than a function-scoped fixture, except that the value getter function is not a fixture and therefore can @@ -762,7 +763,7 @@ def _make_ids(**args): for n, v in args.items(): yield "%s=%s" % (n, mini_idval(val=v, argname='', idx=v)) - idgen = lambda **args: "-".join(_make_ids(**args)) + idgen = lambda **args: "-".join(_make_ids(**args)) # noqa # generate id if idgen is not None: @@ -1118,7 +1119,7 @@ def _get_argnames_argvalues(argnames=None, argvalues=None, **args): argvalues = kw_argvalues # simplify if needed to comply with pytest.mark.parametrize if len(argnames) == 1: - argvalues = [l[0] if not is_marked_parameter_value(l) else l for l in argvalues] + argvalues = [_l[0] if not is_marked_parameter_value(_l) else _l for _l in argvalues] return argnames, argvalues if isinstance(argnames, string_types): @@ -1165,6 +1166,7 @@ def _gen_ids(argnames, argvalues, idgen): raise TypeError("idgen should be a callable or a string, found: %r" % idgen) _formatter = idgen + def gen_id_using_str_formatter(**params): try: # format using the idgen template diff --git a/pytest_cases/plugin.py b/pytest_cases/plugin.py index 62db2cfc..888c197e 100644 --- a/pytest_cases/plugin.py +++ b/pytest_cases/plugin.py @@ -20,14 +20,15 @@ from funcsigs import signature # noqa try: # python 3.3+ type hints - from typing import List, Tuple, Union, Iterable, MutableMapping # noqa - from _pytest.python import CallSpec2, Function + from typing import List, Tuple, Union, Iterable, MutableMapping, Mapping, Optional # noqa + from _pytest.python import CallSpec2 + from _pytest.config import Config except ImportError: pass from .common_mini_six import string_types from .common_pytest_lazy_values import get_lazy_args -from .common_pytest_marks import PYTEST35_OR_GREATER, PYTEST46_OR_GREATER, PYTEST37_OR_GREATER, PYTEST54_OR_GREATER +from .common_pytest_marks import PYTEST35_OR_GREATER, PYTEST46_OR_GREATER, PYTEST37_OR_GREATER from .common_pytest import get_pytest_nodeid, get_pytest_function_scopenum, is_function_node, get_param_names, \ get_param_argnames_as_list @@ -449,12 +450,15 @@ def get_not_always_used(self): for c in self.get_leaves(): j = 0 - for i in range(len(initial_list)): + for _ in range(len(initial_list)): + # get next element in the list (but the list may reduce in size during the loop) fixture_name = initial_list[j] if fixture_name not in c.gather_all_required(): + # Remove element from the list. Therefore, do not increment j del initial_list[j] results_list.append(fixture_name) else: + # Do not remove from the list: increment j j += 1 return results_list @@ -535,7 +539,8 @@ class SuperClosure(MutableSequence): In this implementation, it is backed by a fixture closure tree, that we have to preserve in order to get parametrization right. In another branch of this project ('super_closure' branch) we tried to forget the tree - and only keep the partitions, but parametrization order was not as intuitive for the end user as all unions appeared as parametrized first (since they induced the partitions). + and only keep the partitions, but parametrization order was not as intuitive for the end user as all unions + appeared as parametrized first (since they induced the partitions). """ __slots__ = 'tree', 'all_fixture_defs' @@ -579,8 +584,9 @@ def __repr__(self): alternatives = self.tree.get_alternatives() nb_alternative_closures = len(alternatives) return "SuperClosure with %s alternative closures:\n" % nb_alternative_closures \ - + "\n".join(" - %s (filters: %s)" % (p, ", ".join("%s=%s[%s]=%s" % (k, k, v[0], v[1]) for k, v in f.items())) - for f, p in alternatives) \ + + "\n".join(" - %s (filters: %s)" % (p, ", ".join("%s=%s[%s]=%s" % (k, k, v[0], v[1]) + for k, v in f.items())) + for f, p in alternatives) \ + "\nThe 'super closure list' is %s\n\nThe fixture tree is :\n%s\n" % (list(self), self.tree) def get_all_fixture_defs(self, drop_fake_fixtures=True): @@ -1389,9 +1395,9 @@ def sort_according_to_ref_list(fixturenames, param_names): _SKIP = 'skip' _NORMAL = 'normal' _OPTIONS = { - _NORMAL: """(default) the usual reordering done by pytest to optimize setup/teardown of session- / module- + _NORMAL: """(default) the usual reordering done by pytest to optimize setup/teardown of session- / module- / class- fixtures, as well as all the modifications made by other plugins (e.g. pytest-reorder)""", - _SKIP: """skips *all* reordering, even the one done by pytest itself or installed plugins + _SKIP: """skips *all* reordering, even the one done by pytest itself or installed plugins (e.g. pytest-reorder)""" } @@ -1407,7 +1413,7 @@ def pytest_addoption(parser): # will be loaded when the pytest_configure hook below is called -PYTEST_CONFIG = None # type: _pytest.config.Config +PYTEST_CONFIG = None # type: Optional[Config] def pytest_load_initial_conftests(early_config): diff --git a/setup.cfg b/setup.cfg index 6c2cbc37..809ba95d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -133,7 +133,7 @@ exclude_lines = max-line-length = 120 extend-ignore = D, E203 # D: Docstring errors, E203: see https://github.com/PyCQA/pycodestyle/issues/373 copyright-check = True -copyright-regexp = ^\#\s+Authors:\s+Sylvain MARIE \n\#\s+\+\sAll\scontributors\sto\s\n\#\n\#\s\sLicense:\s3\-clause\sBSD,\s +copyright-regexp = ^\#\s+Authors:\s+Sylvain MARIE \n\#\s+\+\sAll\scontributors\sto\s\n\#\n\#\s+License:\s3\-clause\sBSD,\s exclude = .git .github