⬅ pytest_cases/case_parametrizer_new.py source

1 # Authors: Sylvain MARIE <sylvain.marie@se.com>
2 # + All contributors to <https://github.com/smarie/python-pytest-cases>
3 #
4 # License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
5 # Use true division operator always even in old python 2.x (used in `_extract_cases_from_module`)
6 from __future__ import division
7  
8 from collections import namedtuple
9  
10 import functools
11 from importlib import import_module
12 from inspect import getmembers, ismodule
13 import re
14 from warnings import warn
15  
16 try: # python 3.3+
17 from inspect import signature
18 except ImportError:
19 from funcsigs import signature # noqa
20  
21 try:
22 from typing import Union, Callable, Iterable, Any, Type, List, Tuple # noqa
23 except ImportError:
24 pass
25  
26 from .common_mini_six import string_types
27 from .common_others import get_code_first_line, AUTO, qname, funcopy, needs_binding, get_function_host, \
28 in_same_module, get_host_module, get_class_that_defined_method
29 from .common_pytest_marks import copy_pytest_marks, make_marked_parameter_value, remove_pytest_mark, filter_marks, \
30 get_param_argnames_as_list, Mark
31 from .common_pytest_lazy_values import LazyValue, LazyTuple, LazyTupleItem
32 from .common_pytest import safe_isclass, MiniMetafunc, is_fixture, get_fixture_name, inject_host, add_fixture_params, \
33 list_all_fixtures_in, get_pytest_request_and_item, safe_isinstance
34  
35 from .case_funcs import matches_tag_query, is_case_function, is_case_class, CASE_PREFIX_FUN, copy_case_info, \
36 get_case_id, get_case_marks, GEN_BY_US
37  
38 from .fixture_core1_unions import USED, NOT_USED
39 from .fixture_core2 import CombinedFixtureParamValue, fixture
40 from .fixture__creation import check_name_available, get_caller_module, CHANGE
41 from .fixture_parametrize_plus import fixture_ref, _parametrize_plus, FixtureParamAlternative, ParamAlternative, \
42 SingleParamAlternative, MultiParamAlternative, FixtureRefItem
43  
44 try:
45 ModuleNotFoundError
46 except NameError:
47 # python < 3.6
48 ModuleNotFoundError = ImportError
49  
50  
51 THIS_MODULE = object()
52 """Singleton that can be used instead of a module name to indicate that the module is the current one"""
53  
54 try:
55 from typing import Literal, Optional # noqa
56 from types import ModuleType # noqa
57  
58 ModuleRef = Union[str, ModuleType, Literal[AUTO], Literal[THIS_MODULE]] # noqa
59 CaseType = Union[Callable, Type, ModuleRef]
60  
61 except: # noqa
62 pass
63  
64  
65 _HOST_CLS_ATTR = '_pytestcases_host_cls'
66  
67  
68 def parametrize_with_cases(argnames, # type: Union[str, List[str], Tuple[str, ...]]
69 cases=AUTO, # type: Union[CaseType, List[CaseType]]
70 prefix=CASE_PREFIX_FUN, # type: str
71 glob=None, # type: str
72 has_tag=None, # type: Any
73 filter=None, # type: Callable[..., bool] # noqa
74 ids=None, # type: Union[Callable, Iterable[str]]
75 idstyle=None, # type: Union[str, Callable]
76 # idgen=_IDGEN, # type: Union[str, Callable]
77 debug=False, # type: bool
78 scope="function", # type: str
79 import_fixtures=False # type: bool
80 ):
81 # type: (...) -> Callable[[Callable], Callable]
82 """
83 A decorator for test functions or fixtures, to parametrize them based on test cases. It works similarly to
84 `@pytest.mark.parametrize`: argnames represent a coma-separated string of arguments to inject in the decorated
85 test function or fixture. The argument values (argvalues in `pytest.mark.parametrize`) are collected from the
86 various case functions found according to `cases`, and injected as lazy values so that the case functions are called
87 just before the test or fixture is executed.
88  
89 By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named
90 `test_<name>_cases.py` or if not found, `cases_<name>.py`, where `test_<name>` is the current module name.
91  
92 Finally, the `cases` argument also accepts an explicit case function, cases-containing class, module or module name;
93 or a list of such elements. Note that both absolute and relative module names are supported.
94  
95 Note that `@parametrize_with_cases` collection and parameter creation steps are strictly equivalent to
96 `get_all_cases` + `get_parametrize_args`. This can be handy for debugging purposes.
97  
98 ```python
99 # Collect all cases
100 cases_funs = get_all_cases(f, cases=cases, prefix=prefix, glob=glob, has_tag=has_tag, filter=filter)
101  
102 # Transform the various functions found
103 argvalues = get_parametrize_args(host_class_or_module, cases_funs, debug=False)
104 ```
105  
106 :param argnames: same than in @pytest.mark.parametrize
107 :param cases: a case function, a class containing cases, a module object or a module name string (relative module
108 names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module.
109 `AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `cases_<name>.py`, will be
110 loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of
111 its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in
112 classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the
113 `*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not
114 need to follow this `*Case*` pattern.
115 :param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to
116 denote different kind of cases, for example 'data_', 'algo_', 'user_', etc.
117 :param glob: an optional glob-like pattern for case ids, for example "*_success" or "*_failure". Note that this
118 is applied on the case id, and therefore if it is customized through `@case(id=...)` it should be taken into
119 account.
120 :param has_tag: a single tag or a tuple, set, list of tags that should be matched by the ones set with the `@case`
121 decorator on the case function(s) to be selected.
122 :param filter: a callable receiving the case function and returning `True` or a truth value in case the function
123 needs to be selected.
124 :param ids: optional custom ids, similar to the one in `pytest.mark.parametrize`. Users may either provide an
125 iterable of string ids, or a callable. If a callable is provided it will receive the case functions. Users
126 may wish to use `get_case_id` or other functions in the API to inspect the case functions.
127 :param idstyle: This is mostly for debug. Style of ids to be used in the "union" fixtures generated by
128 `@parametrize` if some cases are transformed into fixtures behind the scenes. `idstyle` possible values are
129 'compact', 'explicit' or None/'nostyle' (default), or a callable. `idstyle` has no effect if no cases are
130 transformed into fixtures. As opposed to `ids`, a callable provided here will receive a `ParamAlternative`
131 object indicating which generated fixture should be used. See `@parametrize` for details.
132 :param scope: the scope of the union fixture to create if `fixture_ref`s are found in the argvalues
133 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures
134 defined in the cases module into the current module.
135 :param debug: a boolean flag to debug what happens behind the scenes
136 :return:
137 """
138 @inject_host
139 def _apply_parametrization(f, host_class_or_module):
140 """ execute parametrization of test function or fixture `f` """
141  
142 # Collect all cases
143 cases_funs = get_all_cases(f, cases=cases, prefix=prefix, glob=glob, has_tag=has_tag, filter=filter)
144  
145 # Build ids from callable if provided.
146 _ids = ids
147 if ids is not None:
148 try:
149 # if this is an iterable, don't do anything
150 iter(ids)
151 except TypeError:
152 # id this is a callable however, use the callable on the case function (not fixture_ref and lazy_values)
153 _ids = tuple(ids(_get_original_case_func(c)[0]) for c in cases_funs)
154  
155 # Transform the various case functions found into `lazy_value` (for case functions not requiring fixtures)
156 # or `fixture_ref` (for case functions requiring fixtures - for them we create associated case fixtures in
157 # `host_class_or_module`)
158 argvalues = get_parametrize_args(host_class_or_module, cases_funs, prefix=prefix,
159 import_fixtures=import_fixtures, debug=debug, scope=scope)
160  
161 # Finally apply parametrization - note that we need to call the private method so that fixture are created in
162 # the right module (not here)
163 _parametrize_with_cases, needs_inject = _parametrize_plus(argnames, argvalues, ids=_ids, idstyle=idstyle,
164 debug=debug, scope=scope)
165  
166 if needs_inject:
167 return _parametrize_with_cases(f, host_class_or_module)
168 else:
169 return _parametrize_with_cases(f)
170  
171 return _apply_parametrization
172  
173  
174 def _get_original_case_func(case_fun # type: Callable
175 ):
176 """
177  
178 :param case_fun:
179 :return: the original case function, and a boolean indicating if it is different from the input
180 """
181 case_in_class = hasattr(case_fun, _HOST_CLS_ATTR)
182 true_case_func = case_fun.func if case_in_class else case_fun
183 return true_case_func, case_in_class
184  
185  
186 def create_glob_name_filter(glob_str # type: str
187 ):
188 """
189 Creates a glob-like matcher for the name of case functions
190 The only special character that is supported is `*` and it can not be
191 escaped. However it can be used multiple times in an expression.
192  
193 :param glob_str: for example `*_success` or `*_*`
194 :return:
195 """
196 # escape all special regex characters, then find the (escaped) stars and turn them into the regex star .*
197 re_str = re.escape(glob_str).replace("\\*", ".*")
198 # add "end" special regex char
199 name_matcher = re.compile(re_str + "$")
200  
201 def _glob_name_filter(case_fun):
202 case_fun_id = get_case_id(case_fun)
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
203 assert case_fun_id is not None
204 return name_matcher.match(case_fun_id)
205  
206 return _glob_name_filter
207  
208  
209 def get_all_cases(parametrization_target=None, # type: Callable
210 cases=AUTO, # type: Union[CaseType, List[CaseType]]
211 prefix=CASE_PREFIX_FUN, # type: str
212 glob=None, # type: str
213 has_tag=None, # type: Union[str, Iterable[str]]
214 filter=None # type: Callable[[Callable], bool] # noqa
215 ):
216 # type: (...) -> List[Callable]
217 """
218 Lists all desired cases for a given `parametrization_target` (a test function or a fixture). This function may be
219 convenient for debugging purposes. See `@parametrize_with_cases` for details on the parameters.
220  
221 :param parametrization_target: either an explicit module object or a function or None. If it's a function, it will
222 use the module it is defined in. If None is given, it will just get the module it was called from.
223 :param cases: a case function, a class containing cases, a module or a module name string (relative module
224 names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module.
225 `AUTO` (default) means that the module named `test_<name>_cases.py` will be loaded, where `test_<name>.py` is
226 the module file of the decorated function. `AUTO2` allows you to use the alternative naming scheme
227 `cases_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag`
228 are selected, including those functions nested in classes following naming pattern `*Case*`. When classes are
229 explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
230 :param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to
231 denote different kind of cases, for example 'data_', 'algo_', 'user_', etc.
232 :param glob: a matching pattern for case ids, for example `*_success` or `*_failure`. The only special character
233 that can be used for now in this pattern is `*`, it can not be escaped, and it can be used several times in the
234 same expression. The pattern should match the entire case id for the case to be selected. Note that this is
235 applied on the case id, and therefore if it is customized through `@case(id=...)` it will be taken into
236 account.
237 :param has_tag: a single tag or a tuple, set, list of tags that should be matched by the ones set with the `@case`
238 decorator on the case function(s) to be selected.
239 :param filter: a callable receiving the case function and returning True or a truth value in case the function
240 needs to be selected.
241 """
242 # Handle single elements
243 if isinstance(cases, string_types):
244 cases = (cases,)
245 else:
246 try:
247 cases = tuple(cases)
248 except TypeError:
249 cases = (cases,)
250  
251 # validate prefix
252 if not isinstance(prefix, str):
253 raise TypeError("`prefix` should be a string, found: %r" % prefix)
254  
255 # validate glob and filter and merge them in a single tuple of callables
256 filters = ()
257 if glob is not None:
258 if not isinstance(glob, string_types):
259 raise TypeError("`glob` should be a string containing a glob-like pattern (not a regex).")
260  
261 filters += (create_glob_name_filter(glob),)
262 if filter is not None:
263 if not callable(filter):
264 raise TypeError(
265 "`filter` should be a callable starting in pytest-cases 0.8.0. If you wish to provide a single"
266 " tag to match, use `has_tag` instead.")
267  
268 filters += (filter,)
269  
270 # parent package
271 if parametrization_target is None:
272 parametrization_target = get_caller_module()
273  
274 if ismodule(parametrization_target):
275 caller_module_name = parametrization_target.__name__
276 elif callable(parametrization_target):
277 caller_module_name = getattr(parametrization_target, '__module__', None)
278 else:
279 raise ValueError("Can't handle parametrization_target=%s" % parametrization_target)
280  
281 parent_pkg_name = '.'.join(caller_module_name.split('.')[:-1]) if caller_module_name is not None else None
282  
283 # start collecting all cases
284 cases_funs = []
285 for c in cases:
286 # load case or cases depending on type
287 if safe_isclass(c):
288 # class - do not check name, it was explicitly passed
289 new_cases = extract_cases_from_class(c, case_fun_prefix=prefix, check_name=False)
290 cases_funs += new_cases
291 elif callable(c):
292 # function
293 if is_case_function(c, check_prefix=False): # do not check prefix, it was explicitly passed
294 # bind it automatically if needed (if unbound class method)
295 shall_bind, bound_c = needs_binding(c, return_bound=True)
296 cases_funs.append(bound_c)
297 else:
298 raise ValueError("Unsupported case function: %r" % c)
299 else:
300 # module
301 if c is AUTO:
302 # Make sure we're in a test_<xxx>.py-like module.
303 # We cannot accept AUTO cases in, e.g., conftest.py
304 # as we don't know what to look for. We complain here
305 # rather than raising AssertionError in the call to
306 # import_default_cases_module. See #309.
307 if not caller_module_name.split('.')[-1].startswith('test_'):
308 raise ValueError(
309 'Cannot use `cases=AUTO` in file "%s". `cases=AUTO` is '
310 'only allowed in files whose name starts with "test_" '
311 % caller_module_name
312 )
313 # First try `test_<name>_cases.py` Then `cases_<name>.py`
314 c = import_default_cases_module(caller_module_name)
315  
316 elif c is THIS_MODULE or c == '.':
317 c = caller_module_name
318  
319 new_cases = extract_cases_from_module(c, package_name=parent_pkg_name, case_fun_prefix=prefix)
320 cases_funs += new_cases
321  
322 # filter last, for easier debugging (collection will be slightly less performant when a large volume of cases exist)
323 return [c for c in cases_funs
324 if matches_tag_query(c, has_tag=has_tag, filter=filters)]
325  
326  
327 def get_parametrize_args(host_class_or_module, # type: Union[Type, ModuleType]
328 cases_funs, # type: List[Callable]
329 prefix, # type: str
330 scope="function", # type: str
331 import_fixtures=False, # type: bool
332 debug=False # type: bool
333 ):
334 # type: (...) -> List[CaseParamValue]
335 """
336 Transforms a list of cases (obtained from `get_all_cases`) into a list of argvalues for `@parametrize`.
337 Each case function `case_fun` is transformed into one or several `lazy_value`(s) or a `fixture_ref`:
338  
339 - If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref`
340 will be returned. The fixture will be created in `host_class_or_module`
341 - If `case_fun` is a parametrized case, one `lazy_value` with a partialized version will be created for each
342 parameter combination.
343 - Otherwise, `case_fun` represents a single case: in that case a single `lazy_value` is returned.
344  
345 :param host_class_or_module: host of the parametrization target. A class or a module.
346 :param cases_funs: a list of case functions, returned typically by `get_all_cases`
347 :param prefix:
348 :param scope:
349 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures
350 defined in the cases module into the current module.
351 :param debug: a boolean flag, turn it to True to print debug messages.
352 :return:
353 """
354 return [c for _f in cases_funs for c in case_to_argvalues(host_class_or_module, _f, prefix, scope, import_fixtures,
355 debug)]
356  
357  
358 class CaseParamValue(object):
359 """Common class for lazy values and fixture refs created from cases"""
360 __slots__ = ()
361  
362 def get_case_id(self):
363 raise NotImplementedError()
364  
365 def get_case_function(self, request):
366 raise NotImplementedError()
367  
368  
369 class _LazyValueCaseParamValue(LazyValue, CaseParamValue):
370 """A case that does not require any fixture is transformed into a `lazy_value` parameter
371 when passed to @parametrize.
372  
373 We subclass it so that we can easily find back all parameter values that are cases
374 """
375  
376 def get_case_id(self):
377 return super(_LazyValueCaseParamValue, self).get_id()
378  
379 def get_case_function(self, request):
380 return _get_original_case_func(self.valuegetter)[0]
381  
382 def as_lazy_tuple(self, nb_params):
383 return _LazyTupleCaseParamValue(self, nb_params)
384  
385  
386 class _LazyTupleCaseParamValue(LazyTuple, CaseParamValue):
387 """A case representing a tuple"""
388  
389 def get_case_id(self):
390 return super(_LazyTupleCaseParamValue, self).get_id()
391  
392 def get_case_function(self, request):
393 return _get_original_case_func(self._lazyvalue.valuegetter)[0]
394  
395  
396 class _FixtureRefCaseParamValue(fixture_ref, CaseParamValue):
397 """A case that requires at least a fixture is transformed into a `fixture_ref` parameter
398 when passed to @parametrize"""
399  
400 def get_case_id(self):
401 return self.get_name_for_id()
402  
403 def get_case_function(self, request):
404 # get the case function copy, or copy of the partial
405 f = request._arg2fixturedefs[self.fixture][0].func
406  
407 # extract the actual original case
408 return f.__origcasefun__
409  
410  
411 def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType]
412 case_fun, # type: Callable
413 prefix, # type: str
414 scope, # type: str
415 import_fixtures=False, # type: bool
416 debug=False # type: bool
417 ):
418 # type: (...) -> Tuple[CaseParamValue, ...]
419 """Transform a single case into one or several `lazy_value`(s) or a `fixture_ref` to be used in `@parametrize`
420  
421 If `case_fun` requires at least on fixture, a fixture will be created if not yet present, and a `fixture_ref` will
422 be returned.
423  
424 If `case_fun` is a parametrized case, (NEW since 3.0.0) a fixture will be created if not yet present,
425 and a `fixture_ref` will be returned. (OLD < 3.0.0) one `lazy_value` with a partialized version will be created
426 for each parameter combination.
427  
428 Otherwise, `case_fun` represents a single case: in that case a single `lazy_value` is returned.
429  
430 :param case_fun:
431 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures
432 defined in the cases module into the current module.
433 :return:
434 """
435 # get the id from the case function either added by the @case decorator, or default one.
436 case_id = get_case_id(case_fun, prefix_for_default_ids=prefix)
437  
438 # get the list of all calls that pytest *would* have made for such a (possibly parametrized) function
439 meta = MiniMetafunc(case_fun)
440  
441 if not meta.requires_fixtures and not meta.is_parametrized:
442 # only retrieve the extra marks added with @case, since the others will be automatically retrieved by the
443 # lazy_value.
444 case_marks = get_case_marks(case_fun, as_decorators=True)
445  
446 # if not meta.is_parametrized:
447 # single unparametrized case function
448 if debug:
449 case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun)
  • T001 Print found.
450 print("Case function %s > 1 lazy_value() with id %s and additional marks %s"
451 % (case_fun_str, case_id, case_marks))
452 return (_LazyValueCaseParamValue(case_fun, id=case_id, marks=case_marks),)
453 # else:
454 # THIS WAS A PREMATURE OPTIMIZATION WITH MANY SHORTCOMINGS. For example what if the case function is
455 # itself parametrized with lazy values ? Let's consider that a parametrized case should be a fixture,
456 # for now
457 #
458 # # parametrized. create one version of the callable for each parametrized call
459 # # do not forget to merge the marks !
460 # if debug:
461 # case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun)
462 # print("Case function %s > tuple of lazy_value() with ids %s and additional marks %s"
463 # % (case_fun_str, ["%s-%s" % (case_id, c.id) for c in meta._calls],
464 # [case_marks + tuple(c.marks) for c in meta._calls]))
465 # return tuple(lazy_value(functools.partial(case_fun, **c.funcargs),
466 # id="%s-%s" % (case_id, c.id), marks=case_marks + tuple(c.marks))
467 # for c in meta._calls)
468 else:
469 # at least 1 required fixture (direct req or through @pytest.mark.usefixtures ), OR parametrized.
470  
471 # if meta.is_parametrized:
472 # # nothing to do, the parametrization marks are on the fixture to create so they will be taken into account
473  
474 # create or reuse a fixture in the host (pytest collector: module or class) of the parametrization target
475 fix_name, remaining_marks = get_or_create_case_fixture(case_id, case_fun, host_class_or_module,
476 meta.fixturenames_not_in_sig, scope,
477 import_fixtures=import_fixtures, debug=debug)
478  
479 # reference that case fixture, and preserve the case id in the associated id whatever the generated fixture name
480 argvalues = _FixtureRefCaseParamValue(fix_name, id=case_id)
481 if debug:
482 case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun)
  • T001 Print found.
483 print("Case function %s > fixture_ref(%r) with marks %s" % (case_fun_str, fix_name, remaining_marks))
484 # return a length-1 tuple because there is a single case created
485 return (make_marked_parameter_value((argvalues,), marks=remaining_marks) if remaining_marks else argvalues,)
486  
487  
488 def get_or_create_case_fixture(case_id, # type: str
489 case_fun, # type: Callable
490 target_host, # type: Union[Type, ModuleType]
491 add_required_fixtures, # type: Iterable[str]
492 scope, # type: str
493 import_fixtures=False, # type: bool
494 debug=False # type: bool
495 ):
496 # type: (...) -> Tuple[str, Tuple[Mark]]
497 """
498 When case functions require fixtures, we want to rely on pytest to inject everything. Therefore
499 we create a "case fixture" wrapping the case function. Since a case function may not be located in the same place
500 than the symbol decorated with @parametrize_with_cases, we create that "case fixture" in the
501 appropriate module/class (the host of the test/fixture function, `target_host`).
502  
503 If the case is parametrized, the parametrization marks are put on the created fixture.
504  
505 If the case has other marks, they are returned as the
506  
507 Note that we create a small cache in the module/class in order to reuse the created fixture corresponding
508 to a case function if it was already required by a test/fixture in this host.
509  
510 :param case_id:
511 :param case_fun:
512 :param target_host:
513 :param add_required_fixtures:
514 :param import_fixtures: experimental feature. Turn this to True in order to automatically import all fixtures
515 defined in the cases module into the current module.
516 :param debug:
517 :return: the newly created fixture name, and the remaining marks not applied
518 """
519 if is_fixture(case_fun):
520 raise ValueError("A case function can not be decorated as a `@fixture`. This seems to be the case for"
521 " %s. If you did not decorate it but still see this error, please report this issue"
522 % case_fun)
523  
524 # source: detect a functools.partial wrapper created by us because of a host class
525 true_case_func, case_in_class = _get_original_case_func(case_fun)
526 true_case_func_host = get_function_host(true_case_func)
527  
528 # for checks
529 orig_name = true_case_func.__name__
530 orig_case = true_case_func
531  
532 # destination
533 target_in_class = safe_isclass(target_host)
534 fix_cases_dct, imported_fixtures_list = _get_fixture_cases(target_host) # get our "storage unit" in this module
535  
536 # shortcut if the case fixture is already known/registered in target host
537 try:
538 fix_name, marks = fix_cases_dct[(true_case_func, scope)]
539 if debug:
  • T001 Print found.
540 print("Case function %s > Reusing fixture %r and marks %s" % (qname(true_case_func), fix_name, marks))
541 return fix_name, marks
542 except KeyError:
543 pass
544  
545 # not yet known there. Create a new symbol in the target host :
546 # we need a "free" fixture name, and a "free" symbol name
547 existing_fixture_names = []
548 # -- fixtures in target module or class should not be overridden
549 existing_fixture_names += list_all_fixtures_in(target_host, recurse_to_module=False)
550 # -- are there fixtures in source module or class ? should not be overridden too
551 if not in_same_module(target_host, true_case_func_host):
552 fixtures_in_cases_module = list_all_fixtures_in(true_case_func_host, recurse_to_module=False)
553 if len(fixtures_in_cases_module) > 0:
554 # EXPERIMENTAL we can try to import the fixtures into current module
555 if import_fixtures:
556 from_module = get_host_module(true_case_func_host)
557 if from_module not in imported_fixtures_list:
558 for f in list_all_fixtures_in(true_case_func_host, recurse_to_module=False, return_names=False):
559 f_name = get_fixture_name(f)
560 if (f_name in existing_fixture_names) or (f.__name__ in existing_fixture_names):
561 raise ValueError("Cannot import fixture %r from %r as it would override an existing symbol "
562 "in %r. Please set `@parametrize_with_cases(import_fixtures=False)`"
563 "" % (f, from_module, target_host))
564 target_host_module = target_host if not target_in_class else get_host_module(target_host)
565 setattr(target_host_module, f.__name__, f)
566  
567 imported_fixtures_list.append(from_module)
568  
569 # Fix the problem with "case_foo(foo)" leading to the generated fixture having the same name
570 existing_fixture_names += fixtures_in_cases_module
571  
572 # If the fixture will be injected in a conftest, make sure its name
573 # is unique. Include also its scope to avoid conflicts. See #311.
574 # Notice target_host.__name__ may just be 'conftest' when tests
575 # are simple modules or a more complicated fully qualified name
576 # when the test suite is a package (i.e., with __init__.py). For
577 # example, target_host.__name__ would be 'tests.conftest' when
578 # executing tests from within 'base' in the following tree:
579 # base/
580 # tests/
581 # __init__.py
582 # conftest.py
583 if 'conftest' in target_host.__name__:
584 extra = target_host.__name__.replace('.', '_')
585 case_id = extra + '_' + case_id + '_with_scope_' + scope
586  
587 def name_changer(name, i):
588 return name + '_' * i
589  
590 # start with name = case_id and find a name that does not exist
591 fix_name = check_name_available(target_host, extra_forbidden_names=existing_fixture_names, name=case_id,
592 if_name_exists=CHANGE, name_changer=name_changer)
593  
594 if debug:
  • T001 Print found.
595 print("Case function %s > Creating fixture %r in %s" % (qname(true_case_func), fix_name, target_host))
596  
597 if case_in_class:
598 if target_in_class:
599 # both in class: direct copy of the non-partialized version
600 case_fun = funcopy(true_case_func)
601 else:
602 # case in class and target in module: use the already existing partialized version
603 case_fun = funcopy(case_fun)
604 else:
605 if target_in_class:
606 # case in module and target in class: create a static method
607 case_fun = staticmethod(true_case_func)
608 else:
609 # none in class: direct copy
610 case_fun = funcopy(true_case_func)
611  
612 # place the special attribute __origcasefun__ so that `_FixtureCase.get_case_function` can find it back
613 case_fun.__origcasefun__ = true_case_func
614  
615 # handle @pytest.mark.usefixtures by creating a wrapper where the fixture is added to the signature
616 if add_required_fixtures:
617 # create a wrapper with an explicit requirement for the fixtures. TODO: maybe we should append and not prepend?
618 case_fun = add_fixture_params(case_fun, add_required_fixtures)
619 # remove the `usefixtures` mark: maybe we should leave it as it does no harm ?
620 remove_pytest_mark(case_fun, "usefixtures")
621  
622 # set all parametrization marks on the case function
623 # get the list of all marks on this case
624 case_marks = get_case_marks(case_fun, concatenate_with_fun_marks=True)
625  
626 if case_marks:
627 # remove all parametrization marks from this list since they will be handled here
628 case_marks = filter_marks(case_marks, remove='parametrize')
629  
630 # create a new fixture from a copy of the case function, and place it on the target host
631 new_fix = fixture(name=fix_name, scope=scope)(case_fun)
632 # mark as generated by pytest-cases so that we skip it during cases collection
633 setattr(new_fix, GEN_BY_US, True)
634 setattr(target_host, fix_name, new_fix)
635  
636 # remember it for next time (one per scope)
637 fix_cases_dct[(true_case_func, scope)] = fix_name, case_marks
638  
639 # check that we did not touch the original case
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
640 assert not is_fixture(orig_case)
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
641 assert orig_case.__name__ == orig_name
642  
643 return fix_name, case_marks
644  
645  
646 def _get_fixture_cases(module_or_class # type: Union[ModuleType, Type]
647 ):
648 """
649 Returns our 'storage unit' in a module or class, used to remember the fixtures created from case functions.
650 That way we can reuse fixtures already created for cases, in a given module/class.
651  
652 In addition, the host module of the class, or the module itself, is used to store a list of modules
653 from where we imported fixtures already. This relates to the EXPERIMENTAL `import_fixtures=True` param.
654 """
655 if ismodule(module_or_class):
656 # module: everything is stored in the same place
657 try:
658 cache, imported_fixtures_list = module_or_class._fixture_cases
659 except AttributeError:
660 cache = dict()
661 imported_fixtures_list = []
662 module_or_class._fixture_cases = (cache, imported_fixtures_list)
663 else:
664 # class: on class only the fixtures dict is stored
665 try:
666 cache = module_or_class._fixture_cases
667 except AttributeError:
668 cache = dict()
669 module_or_class._fixture_cases = cache
670  
671 # grab the imported fixtures list from the module host
672 _, imported_fixtures_list = _get_fixture_cases(get_host_module(module_or_class))
673  
674 return cache, imported_fixtures_list
675  
676  
677 def import_default_cases_module(test_module_name):
678 """
679 Implements the `module=AUTO` behaviour of `@parameterize_cases`.
680  
681 `test_module_name` will have the format "test_<module>.py", the associated python module "test_<module>_cases.py"
682 will be loaded to load the cases.
683  
684 If "test_<module>_cases.py" module is not found it looks for the alternate file `cases_<module>.py`.
685  
686 :param test_module_name: the test module
687 :return:
688 """
689 # First try `test_<name>_cases.py`
690 cases_module_name1 = "%s_cases" % test_module_name
691  
692 try:
693 cases_module = import_module(cases_module_name1)
694 except ModuleNotFoundError:
695 # Then try `cases_<name>.py`
696 parts = test_module_name.split('.')
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
697 assert parts[-1][0:5] == 'test_'
698 cases_module_name2 = "%s.cases_%s" % ('.'.join(parts[:-1]), parts[-1][5:])
699 try:
700 cases_module = import_module(cases_module_name2)
701 except ModuleNotFoundError:
702 # Nothing worked
703 raise ValueError("Error importing test cases module to parametrize %r: unable to import AUTO "
704 "cases module %r nor %r. Maybe you wish to import cases from somewhere else ? In that case"
705 " please specify `cases=...`."
706 % (test_module_name, cases_module_name1, cases_module_name2))
707  
708 return cases_module
709  
710  
711 def hasinit(obj):
712 init = getattr(obj, "__init__", None)
713 if init:
714 return init != object.__init__
715  
716  
717 def hasnew(obj):
718 new = getattr(obj, "__new__", None)
719 if new:
720 return new != object.__new__
721  
722  
723 class CasesCollectionWarning(UserWarning):
724 """
725 Warning emitted when pytest cases is not able to collect a file or symbol in a module.
726 """
727 # Note: if we change this, then the symbol MUST be present in __init__ for import, see GH#249
728 __module__ = "pytest_cases"
729  
730  
731 def extract_cases_from_class(cls,
732 check_name=True,
733 case_fun_prefix=CASE_PREFIX_FUN,
734 _case_param_factory=None
735 ):
736 # type: (...) -> List[Callable]
737 """
738 Collects all case functions (methods matching ``case_fun_prefix``) in class ``cls``.
739  
740 Parameters
741 ----------
742 cls : Type
743 A class where to look for case functions. All methods matching ``prefix`` will be returned.
744  
745 check_name : bool
746 If this is ``True`` and class name does not contain the string ``Case``, the class will not be inspected and
747 an empty list will be returned.
748  
749 case_fun_prefix : str
750 A prefix that case functions (class methods) must match to be collected.
751  
752 _case_param_factory :
753 Legacy. Not used.
754  
755 Returns
756 -------
757 cases_lst : List[Callable]
758 A list of collected case functions (class methods).
759 """
760 if is_case_class(cls, check_name=check_name):
761 # see from _pytest.python import pytest_pycollect_makeitem
762  
763 if hasinit(cls):
764 warn(
765 CasesCollectionWarning(
766 "cannot collect cases class %r because it has a "
767 "__init__ constructor"
768 % (cls.__name__, )
769 )
770 )
771 return []
772 elif hasnew(cls):
773 warn(
774 CasesCollectionWarning(
775 "cannot collect test class %r because it has a "
776 "__new__ constructor"
777 % (cls.__name__, )
778 )
779 )
780 return []
781  
782 return _extract_cases_from_module_or_class(cls=cls, case_fun_prefix=case_fun_prefix,
783 _case_param_factory=_case_param_factory)
784 else:
785 return []
786  
787  
788 def extract_cases_from_module(module, # type: Union[str, ModuleRef]
789 package_name=None, # type: str
790 case_fun_prefix=CASE_PREFIX_FUN, # type: str
791 _case_param_factory=None
792 ):
793 # type: (...) -> List[Callable]
794 """
795 Internal method used to create a list of case functions for all cases available from the given module.
796 See `@cases_data`
797  
798 See also `_pytest.python.PyCollector.collect` and `_pytest.python.PyCollector._makeitem` and
799 `_pytest.python.pytest_pycollect_makeitem`: we could probably do this in a better way in pytest_pycollect_makeitem
800  
801 Parameters
802 ----------
803 module : Union[str, ModuleRef]
804 A module where to look for case functions. All functions in the module matching ``prefix`` will be
805 returned. In addition, all classes in the module with ``Case`` in their name will be inspected. For each of
806 them, all methods matching ``prefix`` will be returned too.
807  
808 package_name : Optional[str], default: None
809 If ``module`` is provided as a string, this is a mandatory package full qualified name (e.g. ``a.b.c``) where
810 to import the module from.
811  
812 case_fun_prefix : str
813 A prefix that case functions (including class methods) must match to be collected.
814  
815 _case_param_factory :
816 Legacy. Not used.
817  
818 Returns
819 -------
820 cases : List[Callable]
821 A list of case functions
822 """
823 # optionally import module if passed as module name string
824 if isinstance(module, string_types):
825 try:
826 module = import_module(module, package=package_name)
827 except ModuleNotFoundError as e:
828 raise ModuleNotFoundError(
829 "Error loading cases from module. `import_module(%r, package=%r)` raised an error: %r"
830 % (module, package_name, e)
831 )
832  
833 return _extract_cases_from_module_or_class(module=module, _case_param_factory=_case_param_factory,
834 case_fun_prefix=case_fun_prefix)
835  
836  
837 def _extract_cases_from_module_or_class(module=None, # type: ModuleRef
838 cls=None, # type: Type
839 case_fun_prefix=CASE_PREFIX_FUN, # type: str
840 _case_param_factory=None
841 ): # type: (...) -> List[Callable]
842 """
843 Extracts all case functions from `module` or `cls` (only one non-None must be provided).
844  
845 Parameters
846 ----------
847 module : Optional[ModuleRef], default: None
848 A module where to look for case functions. All functions in the module matching ``prefix`` will be
849 returned. In addition, all classes in the module with ``Case`` in their name will be inspected. For each of
850 them, all methods matching ``prefix`` will be returned too.
851  
852 cls : Optional[Type], default: None
853 A class where to look for case functions. All methods matching ``prefix`` will be returned.
854  
855 case_fun_prefix : str
856 A prefix that case functions (including class methods) must match to be collected.
857  
858 _case_param_factory :
859 Legacy. Not used.
860  
861 Returns
862 -------
863 cases : List[Callable]
864 A list of case functions
865 """
866 if not ((cls is None) ^ (module is None)):
867 raise ValueError("Only one of cls or module should be provided")
868  
869 container = cls or module
870  
871 # We will gather all cases in the reference module and put them in this dict (line no, case)
872 cases_dct = dict()
873  
874 # List members - only keep the functions from the module file (not the imported ones)
875 if module is not None:
876 def _of_interest(f):
877 # check if the function is actually *defined* in this module (not imported from elsewhere)
878 # Note: we used code.co_filename == module.__file__ in the past
879 # but on some targets the file changes to a cached one so this does not work reliably,
880 # see https://github.com/smarie/python-pytest-cases/issues/72
881 try:
882 return f.__module__ == module.__name__
883 except: # noqa
884 return False
885 else:
886 def _of_interest(x): # noqa
887 return True
888  
889 for m_name, m in getmembers(container, _of_interest):
890 if is_case_class(m):
891 co_firstlineno = get_code_first_line(m)
892 cls_cases = extract_cases_from_class(m, case_fun_prefix=case_fun_prefix,
893 _case_param_factory=_case_param_factory)
894 for _i, _m_item in enumerate(cls_cases):
895 gen_line_nb = co_firstlineno + (_i / len(cls_cases))
896 cases_dct[gen_line_nb] = _m_item
897  
898 elif is_case_function(m, prefix=case_fun_prefix):
899 try:
900 # read pytest magic attribute "place_as" to make sure this is placed correctly
901 m_for_placing = m.place_as
902 except AttributeError:
903 # nominal: get the first line of code
904 co_firstlineno = get_code_first_line(m)
905 else:
906 # currently we only support replacing inside the same module
907 if m_for_placing.__module__ != m.__module__:
908 raise ValueError("Unsupported value for 'place_as' special pytest attribute on case function %s: %s"
909 ". Virtual placing in another module is not supported yet by pytest-cases."
910 % (m, m_for_placing))
911 co_firstlineno = get_code_first_line(m_for_placing)
912  
913 if cls is not None:
914 if isinstance(cls.__dict__[m_name], (staticmethod, classmethod)):
915 # no need to partialize a 'self' argument
916 # BUT we'll need to recopy all marks from the holding class to the function
917 # so let's partialize the function to get a safely editable copy of it
918 new_m = functools.partial(m)
919  
920 else:
921 # Make sure that there is at least one argument
922 try:
923 s = signature(m)
924 except Exception: # noqa
925 # ignore any error here, this is optional.
926 pass
927 else:
928 if len(s.parameters) < 1 or (tuple(s.parameters.keys())[0] != "self"):
929 raise TypeError("case method is missing 'self' argument but is not static: %s" % m)
930 # partialize the function to get one without the 'self' argument
931 new_m = functools.partial(m, cls())
932  
933 # Remember the host class. We'll later use this flag to remember that this is a partial.
934 setattr(new_m, _HOST_CLS_ATTR, cls)
935 # Recopy all metadata concerning the case function, since partial does not copy the __dict__ by default
936 new_m.__name__ = m.__name__
937 copy_case_info(m, new_m)
938 copy_pytest_marks(m, new_m, override=True)
939 m = new_m
940 del new_m
941 # Finally, propagate all marks from the holding case class to the case function
942 copy_pytest_marks(cls, m, override=False)
943  
944 if _case_param_factory is None:
945 # Nominal usage: put the case in the dictionary
946 if co_firstlineno in cases_dct:
947 raise ValueError("Error collecting case functions, line number used by %r is already used by %r !"
948 % (m, cases_dct[co_firstlineno]))
949 cases_dct[co_firstlineno] = m
950 else:
951 # Not used anymore
952 # Legacy usage where the cases generators were expanded here and inserted with a virtual line no
953 _case_param_factory(m, co_firstlineno, cases_dct)
954  
955 # convert into a list, taking all cases in order of appearance in the code (sort by source code line number)
956 cases = [cases_dct[k] for k in sorted(cases_dct.keys())]
957  
958 return cases
959  
960  
961 def get_current_params(request_or_item):
962 """
963 Returns a dictionary containing all parameters for the currently active `pytest` item.
964 """
965 # (0) get pytest `request` and `item`
966 item, request = get_pytest_request_and_item(request_or_item)
967  
968 # (1) pre-scan for MultiParamAlternatives to store map of fixturename -> argnames
969 mp_fix_to_args = dict()
970 try:
971 param_items = dict(item.callspec.params)
972 except AttributeError:
973 return {}, {}, {}
974  
975 for argname_or_fixname, param_value in item.callspec.params.items():
976 if isinstance(param_value, MultiParamAlternative):
977 # remember that the fixture named `param_value.alternative_name` represents the multiparam
978 mp_fix_to_args[param_value.alternative_name] = param_value.argnames, param_value.decorated
979 # we can discard this intermediate param now, it is useless
980 del param_items[argname_or_fixname]
981  
982 # (2) now extract all parameters available and their associated information
983 test_fun = request.node.function
984 results_testfun_and_unknown_fixtures = []
985 results_known_fixtures = dict()
986 results_known_fixtures_but_not_found = dict()
987 for argname_or_fixname, param_value in param_items.items():
988 # print(argname_or_fixturename, param_value)
989  
990 if param_value in (NOT_USED, USED):
991 continue # param induced by Fixture Union: ignore
992  
993 elif not safe_isinstance(param_value, CombinedFixtureParamValue):
994 # (a) Parameters on a test function, or parameters on a fixture with a fixture_ref inside (other fixturegen)
995 argnames, actual_value, parametrized = get_current_param(param_value, argname_or_fixname, mp_fix_to_args)
996 # - In nominal, we receive each (argname, value) pair independently and argnames = (argname_or_fixturename,)
997 # - If a @parametrize containing `fixture_ref`s is present, various new parameters are received and the
998 # `argname_or_fixturename` does not represent something useful. In this case, `argnames` may have length > 1
999  
1000 # Save each parameter one by one now
1001 for i, _argname in enumerate(argnames):
1002 _val = actual_value[i] if len(argnames) > 1 else actual_value
1003 if parametrized is None:
1004 # we are not able to know if the parameter is for the test function or a fixture
1005 results_testfun_and_unknown_fixtures.append((_argname, _val))
1006 elif _is_same_parametrized_target(parametrized, test_fun):
1007 # the parameter is for the test function
1008 results_testfun_and_unknown_fixtures.append((_argname, _val))
1009 else:
1010 # we are able to know that the parameter is for a fixture, but can we find that fixture ?
1011 try:
1012 fixname = _find_fixture_name(parametrized)
1013 except Exception:
1014 # we can't find the fixture. add it to the dict of "not found"
1015 # this is probably related to the fact that this is a case function or a dynamically
1016 # created fixture
1017 results_known_fixtures_but_not_found.setdefault(parametrized, []).append((_argname, _val))
1018 else:
1019 results_known_fixtures.setdefault(fixname, []).append((_argname, _val))
1020 else:
1021 # (b) (Combined) parameters on a fixture, except those including fixture_refs
1022 fixturename = argname_or_fixname
1023 # de-combine each distinct @parametrize that was made on that fixture
1024 for argnames, argvals in param_value.iterparams():
1025 # this is a single @parametrize(argnames, argvals)
1026 # note: do not iterate on the argvals but on the argnames, as argvals can be a LazyTuple
1027 for item, argname in enumerate(argnames):
1028 value = argvals[item] if len(argnames) > 1 else argvals # argvals is already unpacked if single
1029 _name, actual_value, _target = get_current_param(value, fixturename, mp_fix_to_args)
1030 # the parameter is for a fixture
1031 # if argname != _names[0] or len(_names) > 1:
1032 # get_current_param(value, fixturename, mp_fix_to_args, test_fun_name)
1033 # raise ValueError("Please report")
1034 results_known_fixtures.setdefault(fixturename, []).append((argname, actual_value))
1035  
1036 # process the lists to create the outputs
1037 # First, the test function params and the legacy pytest fixture params (if not hidden by names of fun params)
1038 tests_and_legacy_fix_results_dict = dict(results_testfun_and_unknown_fixtures)
1039 if len(tests_and_legacy_fix_results_dict) != len(results_testfun_and_unknown_fixtures):
1040 raise ValueError("Error: multiple values found for the same parameter. Please report this issue")
1041  
1042 # Then new style fixtures. since in some cases fixture names can conflict with param names, we use a separate dict.
1043 fixture_results_dict = dict()
1044 for fixture_name, results_list in results_known_fixtures.items():
1045 fixture_results_dct = dict(results_list)
1046 if len(fixture_results_dct) != len(results_list):
1047 raise ValueError("Error: multiple values found for the same fixture parameter. Please report this issue")
1048 fixture_results_dict[fixture_name] = fixture_results_dct
1049  
1050 # the remainder: fixtures that can't be found.
1051 results_unknown_dict = dict()
1052 for function, results_list in results_known_fixtures_but_not_found.items():
1053 fixture_results_dct = dict(results_list)
1054 if len(fixture_results_dct) != len(results_list):
1055 raise ValueError("Error: multiple values found for the same parameter. Please report this issue")
1056 results_unknown_dict[function] = fixture_results_dct
1057  
1058 return tests_and_legacy_fix_results_dict, fixture_results_dict, results_unknown_dict
1059  
1060  
1061 def _is_same_parametrized_target(parametrized, test_fun):
1062 """
1063  
1064 :param parametrized:
1065 :param test_fun:
1066 :return:
1067 """
1068 return parametrized.__name__ == test_fun.__name__
1069  
1070  
1071 def _find_fixture_name(parametrized):
1072 """
1073 Finds the actual fixture symbol whose implementation is this function.
1074 :param parametrized:
1075 :return:
1076 """
1077 container = get_class_that_defined_method(parametrized)
1078 if container is None:
1079 container = get_function_host(parametrized)
1080  
1081 parametrized_fixture = getattr(container, parametrized.__name__)
1082  
1083 return get_fixture_name(parametrized_fixture)
1084  
1085  
1086 def get_current_param(value, argname_or_fixturename, mp_fix_to_args):
1087 """
1088 This function's primary role is to unpack the various parameter values (instances of `ParamAlternative`) created by
1089 @parametrize when a fixture reference is used in the parametrization.
1090  
1091 Returns the argnames, actual value, and parametrized fixture name if it can be known,
1092 associated with parameter value `value`.
1093  
1094 :param value:
1095 :param argname_or_fixturename:
1096 :param mp_fix_to_args:
1097 :return: (argnames, actual_value, paramztrized_fixname)
1098 """
1099 try:
1100 # (1) Does this parameter correspond to a fixture *generated* by a MultiParamAlternative ?
1101 # If so we already have its true argnames and parametrization target here, and the value is directly the param.
1102 argnames, parametrized = mp_fix_to_args[argname_or_fixturename]
1103 actual_value = value
1104 except KeyError:
1105 # (2) Is this parameter a ParamAlternative? (this happens when at least 1 param in the argvals is a fixture_ref)
1106 if safe_isinstance(value, ParamAlternative):
1107 # if isinstance(value, MultiParamAlternative):
1108 # return False # ignore silently, already handled in the pass before the main loop
1109 if isinstance(value, SingleParamAlternative):
1110 # extract the various info available
1111 parametrized = value.decorated
1112 # actual_id = value.get_alternative_id()
1113 argnames = value.argnames
1114 actual_value = value.argval
1115 if len(argnames) == 1 and not isinstance(value, FixtureParamAlternative):
1116 actual_value = actual_value[0]
1117 else:
1118 raise TypeError("Unsupported type, please report: %r" % type(value))
1119 else:
1120 # (3) "normal" parameter: each (argname, value) pair is received independently
1121 argnames = (argname_or_fixturename,)
1122 parametrized = None
1123 actual_value = value
1124  
1125 return argnames, actual_value, parametrized
1126  
1127  
1128 Case = namedtuple("Case", ("id", "func", "params"))
1129  
1130  
1131 def get_current_cases(request_or_item):
1132 """
1133 Returns a dictionary containing all case parameters for the currently active `pytest` item.
1134 You can either pass the `pytest` item (available in some hooks) or the `request` (available in hooks, and also
1135 directly as a fixture).
1136  
1137 For each test function argument parametrized using a `@parametrize_with_case(<argname>, ...)` this dictionary
1138 contains an entry `{<argname>: (case_id, case_function, case_params)}`. If several argnames are parametrized this
1139 way, a dedicated entry will be present for each argname. The tuple is a `namedtuple` containing
1140  
1141 - `id` a string containing the actual case id constructed by `@parametrize_with_cases`.
1142 - `function` the original case function.
1143 - `params` a dictionary, containing the parameters of the case, if itself is parametrized. Note that if the
1144 case is parametrized with `@parametrize_with_cases`, the associated parameter value in the dictionary will also be
1145 `(actual_id, case_function, case_params)`.
1146  
1147 If a fixture parametrized with cases is active, the dictionary will contain an entry `{<fixturename>: <dct>}` where
1148 `<dct>` is a dictionary `{<argname>: (case_id, case_function, case_params)}`.
1149  
1150 To get more information on a case function, you can use `get_case_marks(f)`, `get_case_tags(f)`.
1151 You can also use `matches_tag_query` to check if a case function matches some expectations either concerning its id
1152 or its tags. See https://smarie.github.io/python-pytest-cases/#filters-and-tags
1153  
1154 Note that you can get the same contents directly by using the `current_cases` fixture.
1155 """
1156 # (0) get pytest `request` and `item`
1157 item, request = get_pytest_request_and_item(request_or_item)
1158  
1159 # (1) retrieve all parameters
1160 test_res_dict, fixture_results_dict, res_unkfix_dict = get_current_params(request_or_item)
1161  
1162 # multiple nesyed @parametrize with fixture refs might have created several wrappers. access the
1163 res_unkfix_dict2 = {_get_place_as(k): v for k, v in res_unkfix_dict.items()}
1164  
1165 # Now create the results containing the cases and their parameters only
1166 case_fixture_names_to_remove = set()
1167  
1168 def _do(name, value, dct, preserve=False):
1169 if safe_isinstance(value, LazyTupleItem):
1170 value = value.host._lazyvalue
1171 elif safe_isinstance(value, FixtureRefItem):
1172 value = value.host
1173  
1174 if safe_isinstance(value, CaseParamValue):
1175 # Case function
1176 case_func = value.get_case_function(request)
1177  
1178 # Case id
1179 # we cannot use `get_case_id` because we do not know the prefix that was used
1180 # case_id = get_case_id(case_func, prefix_for_default_ids=)
1181 case_id = value.get_case_id()
1182  
1183 # Case parameter(s)
1184 case_params_dct = {}
1185 if safe_isinstance(value, _FixtureRefCaseParamValue):
1186 casefixname = value.fixture
1187 if casefixname in fixture_results_dict:
1188 # case is a fixture and is parametrized 1
1189 case_fixture_names_to_remove.add(casefixname)
1190 for _n, _v in fixture_results_dict[casefixname].items():
1191 _do(_n, _v, case_params_dct, preserve=True)
1192 else:
1193 case_impl_fun = _get_place_as(case_func)
1194 try:
1195 paramz = res_unkfix_dict2[case_impl_fun]
1196 except KeyError:
1197 # case is a fixture but is not parametrized
1198 pass
1199 else:
1200 # case is a fixture and is parametrized 2
1201 # it was harder to find its params because they did not directly link to the fixture
1202 for _n, _v in paramz.items():
1203 _do(_n, _v, case_params_dct, preserve=True)
1204 else:
1205 # case is not a fixture: it cannot possibly be parametrized
1206 pass
1207  
1208 # Finally fill the results
1209 dct[name] = Case(case_id, case_func, case_params_dct)
1210  
1211 elif preserve:
1212 # used in nested scenarii
1213 dct[name] = value
1214  
1215 cases_res_dict = dict()
1216 for name, value in test_res_dict.items():
1217 # fill the main dict
1218 _do(name, value, cases_res_dict)
1219  
1220 # use a separate dict as name conflicts might happen
1221 cases_res_dict_fixs = dict()
1222 for name, value in fixture_results_dict.items():
1223 # fill a dedicated subdict
1224 sub_dict = {}
1225 for n, v in value.items():
1226 _do(n, v, sub_dict)
1227 if len(sub_dict) > 0:
1228 cases_res_dict_fixs[name] = sub_dict
1229  
1230 # finally remove the case fixtures from the result dict
1231 for f in case_fixture_names_to_remove:
1232 try:
1233 del cases_res_dict_fixs[f]
1234 except KeyError:
1235 pass
1236  
1237 # merge the two - put the fixtures at the end
1238 for k, v in cases_res_dict_fixs.items():
1239 if k not in cases_res_dict:
1240 cases_res_dict[k] = v
1241  
1242 return cases_res_dict
1243  
1244  
1245 def _get_place_as(f):
1246 while True:
1247 try:
1248 f = f.place_as
1249 except AttributeError:
1250 return f
1251  
1252  
1253 def get_current_case_id(request_or_item,
1254 argnames # type: Union[Iterable[str], str]
1255 ):
1256 """ DEPRECATED - use `get_current_cases` instead
1257 A helper function to return the current case id for a given `pytest` item (available in some hooks) or `request`
1258 (available in hooks, and also directly as a fixture).
1259  
1260 You need to provide the argname(s) used in the corresponding `@parametrize_with_cases` so that this method finds
1261 the right id.
1262  
1263 :param request_or_item:
1264 :param argnames:
1265 :return:
1266 """
1267 warn("`get_current_case_id` is DEPRECATED - please use the `current_cases` fixture instead, or `get_current_cases`")
1268  
1269 # process argnames
1270 if isinstance(argnames, string_types):
1271 argnames = get_param_argnames_as_list(argnames)
1272  
1273 # retrieve the correct id
1274 all_case_funcs = get_current_cases(request_or_item)
1275 return all_case_funcs[argnames[0]][0]
1276  
1277  
1278 # Below is the beginning of a switch from our code scanning tool above to the same one than pytest.
1279 # from .common_pytest import is_fixture, safe_isclass, compat_get_real_func, compat_getfslineno
1280 #
1281 #
1282 # class PytestCasesWarning(UserWarning):
1283 # """
1284 # Bases: :class:`UserWarning`.
1285 #
1286 # Base class for all warnings emitted by pytest cases.
1287 # """
1288 #
1289 # __module__ = "pytest_cases"
1290 #
1291 #
1292 # class PytestCasesCollectionWarning(PytestCasesWarning):
1293 # """
1294 # Bases: :class:`PytestCasesWarning`.
1295 #
1296 # Warning emitted when pytest cases is not able to collect a file or symbol in a module.
1297 # """
1298 #
1299 # __module__ = "pytest_cases"
1300 #
1301 #
1302 # class CasesModule(object):
1303 # """
1304 # A collector for test cases
1305 # This is a very lightweight version of `_pytest.python.Module`,the pytest collector for test functions and classes.
1306 #
1307 # See also pytest_collect_file and pytest_pycollect_makemodule hooks
1308 # """
1309 # __slots__ = 'obj'
1310 #
1311 # def __init__(self, module):
1312 # self.obj = module
1313 #
1314 # def collect(self):
1315 # """
1316 # A copy of pytest Module.collect (PyCollector.collect actually)
1317 # :return:
1318 # """
1319 # if not getattr(self.obj, "__test__", True):
1320 # return []
1321 #
1322 # # NB. we avoid random getattrs and peek in the __dict__ instead
1323 # # (XXX originally introduced from a PyPy need, still true?)
1324 # dicts = [getattr(self.obj, "__dict__", {})]
1325 # for basecls in getmro(self.obj.__class__):
1326 # dicts.append(basecls.__dict__)
1327 # seen = {}
1328 # values = []
1329 # for dic in dicts:
1330 # for name, obj in list(dic.items()):
1331 # if name in seen:
1332 # continue
1333 # seen[name] = True
1334 # res = self._makeitem(name, obj)
1335 # if res is None:
1336 # continue
1337 # if not isinstance(res, list):
1338 # res = [res]
1339 # values.extend(res)
1340 #
1341 # def sort_key(item):
1342 # fspath, lineno, _ = item.reportinfo()
1343 # return (str(fspath), lineno)
1344 #
1345 # values.sort(key=sort_key)
1346 # return values
1347 #
1348 # def _makeitem(self, name, obj):
1349 # """ An adapted copy of _pytest.python.pytest_pycollect_makeitem """
1350 # if safe_isclass(obj):
1351 # if self.iscaseclass(obj, name):
1352 # raise ValueError("Case classes are not yet supported: %r" % obj)
1353 # elif self.iscasefunction(obj, name):
1354 # # mock seems to store unbound methods (issue473), normalize it
1355 # obj = getattr(obj, "__func__", obj)
1356 # # We need to try and unwrap the function if it's a functools.partial
1357 # # or a functools.wrapped.
1358 # # We mustn't if it's been wrapped with mock.patch (python 2 only)
1359 # if not (isfunction(obj) or isfunction(compat_get_real_func(obj))):
1360 # filename, lineno = compat_getfslineno(obj)
1361 # warn_explicit(
1362 # message=PytestCasesCollectionWarning(
1363 # "cannot collect %r because it is not a function." % name
1364 # ),
1365 # category=None,
1366 # filename=str(filename),
1367 # lineno=lineno + 1,
1368 # )
1369 # elif getattr(obj, "__test__", True):
1370 # if isgeneratorfunction(obj):
1371 # filename, lineno = compat_getfslineno(obj)
1372 # warn_explicit(
1373 # message=PytestCasesCollectionWarning(
1374 # "cannot collect %r because it is a generator function." % name
1375 # ),
1376 # category=None,
1377 # filename=str(filename),
1378 # lineno=lineno + 1,
1379 # )
1380 # else:
1381 # res = list(self._gencases(name, obj))
1382 # outcome.force_result(res)
1383 #
1384 # def iscasefunction(self, obj, name):
1385 # """Similar to PyCollector.istestfunction"""
1386 # if name.startswith("case_"):
1387 # if isinstance(obj, staticmethod):
1388 # # static methods need to be unwrapped
1389 # obj = getattr(obj, "__func__", False)
1390 # return (
1391 # getattr(obj, "__call__", False)
1392 # and not is_fixture(obj) is None
1393 # )
1394 # else:
1395 # return False
1396 #
1397 # def iscaseclass(self, obj, name):
1398 # """Similar to PyCollector.istestclass"""
1399 # return name.startswith("Case")
1400 #
1401 # def _gencases(self, name, funcobj):
1402 # # generate the case associated with a case function object.
1403 # # note: the original PyCollector._genfunctions has a "metafunc" mechanism here, we do not need it.
1404 # return []
1405 #
1406 #