Skip to content

Commit fe3c286

Browse files
author
Sylvain MARIE
committed
New helper function get_current_cases and fixture current_cases. Fixes #195
1 parent 2adabb3 commit fe3c286

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

pytest_cases/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .case_funcs import case, copy_case_info, set_case_id, get_case_id, get_case_marks, \
2121
get_case_tags, matches_tag_query, is_case_class, is_case_function
2222
from .case_parametrizer_new import parametrize_with_cases, THIS_MODULE, get_all_cases, get_parametrize_args, \
23-
get_current_case_id
23+
get_current_case_id, get_current_cases
2424

2525
try:
2626
# -- Distribution mode --
@@ -57,5 +57,6 @@
5757
'case', 'copy_case_info', 'set_case_id', 'get_case_id', 'get_case_marks',
5858
'get_case_tags', 'matches_tag_query', 'is_case_class', 'is_case_function',
5959
# test functions
60-
'get_all_cases', 'parametrize_with_cases', 'THIS_MODULE', 'get_parametrize_args', 'get_current_case_id'
60+
'get_all_cases', 'parametrize_with_cases', 'THIS_MODULE', 'get_parametrize_args', 'get_current_case_id',
61+
'get_current_cases'
6162
]

pytest_cases/case_parametrizer_new.py

Lines changed: 93 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121
in_same_module, get_host_module
2222
from .common_pytest_marks import copy_pytest_marks, make_marked_parameter_value, remove_pytest_mark, filter_marks, \
2323
get_param_argnames_as_list
24-
from .common_pytest_lazy_values import lazy_value, LazyTupleItem
24+
from .common_pytest_lazy_values import LazyValue, LazyTuple, LazyTupleItem
2525
from .common_pytest import safe_isclass, MiniMetafunc, is_fixture, get_fixture_name, inject_host, add_fixture_params, \
2626
list_all_fixtures_in
2727

2828
from . import fixture
2929
from .case_funcs import matches_tag_query, is_case_function, is_case_class, CASE_PREFIX_FUN, copy_case_info, \
3030
get_case_id, get_case_marks, GEN_BY_US
3131
from .fixture__creation import check_name_available, CHANGE
32-
from .fixture_parametrize_plus import fixture_ref, _parametrize_plus
32+
from .fixture_parametrize_plus import fixture_ref, _parametrize_plus, UnionFixtureAlternative
3333

3434
try:
3535
ModuleNotFoundError
@@ -322,6 +322,37 @@ def get_parametrize_args(host_class_or_module, # type: Union[Type, ModuleType
322322
debug)]
323323

324324

325+
class CaseParameter(object):
326+
"""Common class for lazy values and fixture refs created from cases"""
327+
__slots__ = ()
328+
329+
def get_case_function(self, request):
330+
raise NotImplementedError()
331+
332+
333+
class _NonFixtureCase(LazyValue, CaseParameter):
334+
"""A case that does not require any fixture is transformed into a `lazy_value` parameter
335+
when passed to @parametrize.
336+
337+
We subclass it so that we can easily find back all parameter values that are cases
338+
"""
339+
340+
def get_case_function(self, request):
341+
return self.valuegetter
342+
343+
344+
class _FixtureCase(fixture_ref, CaseParameter):
345+
"""A case that requires at least a fixture is transformed into a `fixture_ref` parameter
346+
when passed to @parametrize"""
347+
348+
def get_case_function(self, request):
349+
# get the case function copy, or copy of the partial
350+
f = request._arg2fixturedefs[self.fixture][0].func
351+
352+
# extract the actual original case
353+
return f.__origcasefun__
354+
355+
325356
def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType]
326357
case_fun, # type: Callable
327358
prefix, # type: str
@@ -362,7 +393,7 @@ def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType]
362393
case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun)
363394
print("Case function %s > 1 lazy_value() with id %s and additional marks %s"
364395
% (case_fun_str, case_id, case_marks))
365-
return (lazy_value(case_fun, id=case_id, marks=case_marks),)
396+
return (_NonFixtureCase(case_fun, id=case_id, marks=case_marks),)
366397
# else:
367398
# THIS WAS A PREMATURE OPTIMIZATION WITH MANY SHORTCOMINGS. For example what if the case function is
368399
# itself parametrized with lazy values ? Let's consider that a parametrized case should be a fixture,
@@ -390,7 +421,7 @@ def case_to_argvalues(host_class_or_module, # type: Union[Type, ModuleType]
390421
import_fixtures=import_fixtures, debug=debug)
391422

392423
# reference that case fixture, and preserve the case id in the associated id whatever the generated fixture name
393-
argvalues = fixture_ref(fix_name, id=case_id)
424+
argvalues = _FixtureCase(fix_name, id=case_id)
394425
if debug:
395426
case_fun_str = qname(case_fun.func if isinstance(case_fun, functools.partial) else case_fun)
396427
print("Case function %s > fixture_ref(%r) with marks %s" % (case_fun_str, fix_name, remaining_marks))
@@ -507,6 +538,9 @@ def name_changer(name, i):
507538
# none in class: direct copy
508539
case_fun = funcopy(true_case_func)
509540

541+
# place the special attribute __origcasefun__ so that `_FixtureCase.get_case_function` can find it back
542+
case_fun.__origcasefun__ = true_case_func
543+
510544
# handle @pytest.mark.usefixtures by creating a wrapper where the fixture is added to the signature
511545
if add_required_fixtures:
512546
# create a wrapper with an explicit requirement for the fixtures. TODO: maybe we should append and not prepend?
@@ -779,10 +813,60 @@ def _of_interest(x): # noqa
779813
return cases
780814

781815

816+
def get_current_cases(request_or_item):
817+
"""
818+
Returns a dictionary of {argname: (actual_id, case_function)} for a given `pytest` item. The `actual_id`
819+
might differ from the case_id defined on the case, since it might be overridden through pytest cusomtization.
820+
To get more information on the case function, you can use `get_case_id(f)`, `get_case_marks(f)`, `get_case_tags(f)`.
821+
822+
You can also use `matches_tag_query` to check if a case function matches some expectations either concerning its id
823+
or its tags. See https://smarie.github.io/python-pytest-cases/#filters-and-tags
824+
825+
You can either pass the `pytest` item (available in some hooks) or the `request` (available in hooks, and also
826+
directly as a fixture).
827+
828+
Note that you can get the same contents directly by using the `current_cases` fixture.
829+
"""
830+
try:
831+
item = request_or_item.node
832+
except AttributeError:
833+
item = request_or_item
834+
request = item._request
835+
else:
836+
request = request_or_item
837+
838+
results = dict()
839+
for param_or_fixture_name, current_param_value in item.callspec.params.items():
840+
841+
# First, unpack possible lazy tuples
842+
if isinstance(current_param_value, LazyTupleItem):
843+
# a non-fixture case that corresponds to several arguments. There will be an entry for each argument
844+
current_param_value = current_param_value.host._lazyvalue
845+
846+
if isinstance(current_param_value, CaseParameter):
847+
# a non-fixture case : a `lazy_value`
848+
case_func = current_param_value.get_case_function(request)
849+
actual_id = current_param_value.get_id()
850+
results[param_or_fixture_name] = (actual_id, case_func)
851+
852+
elif isinstance(current_param_value, UnionFixtureAlternative):
853+
# a fixture case: we have to dig one level more in order to access the actual `fixture_ref`
854+
# Also for consistency with the non-fixture parameters, we create an entry for each argname
855+
actual_id = current_param_value.get_alternative_id()
856+
case_func = current_param_value.argval.get_case_function(request)
857+
for argname in current_param_value.argnames:
858+
results[argname] = (actual_id, case_func)
859+
860+
elif isinstance(current_param_value, LazyTuple):
861+
raise TypeError("This should not happen, please report")
862+
863+
return results
864+
865+
782866
def get_current_case_id(request_or_item,
783867
argnames # type: Union[Iterable[str], str]
784868
):
785-
"""
869+
""" DEPRECATED - use `get_current_cases` instead
786870
A helper function to return the current case id for a given `pytest` item (available in some hooks) or `request`
787871
(available in hooks, and also directly as a fixture).
788872
@@ -793,29 +877,15 @@ def get_current_case_id(request_or_item,
793877
:param argnames:
794878
:return:
795879
"""
796-
try:
797-
item = request_or_item.node
798-
except AttributeError:
799-
item = request_or_item
880+
warn("`get_current_case_id` is DEPRECATED - please use the `current_cases` fixture instead, or `get_current_cases`")
800881

801882
# process argnames
802883
if isinstance(argnames, string_types):
803884
argnames = get_param_argnames_as_list(argnames)
804-
argnames_str = '_'.join(argnames).replace(' ', '')
805885

806-
try:
807-
# A LazyValue or LazyTupleItem ?
808-
lazy_val = item.callspec.params[argnames[0]]
809-
except KeyError:
810-
# No: A fixture union created by `parametrize_plus_decorate`
811-
main_fixture_style_template = "%s_%s"
812-
fixture_union_name = main_fixture_style_template % (item.function.__name__, argnames_str)
813-
return item.callspec.params[fixture_union_name].get_alternative_id()
814-
else:
815-
# A LazyValue or LazyTupleItem - confirmed.
816-
if isinstance(lazy_val, LazyTupleItem):
817-
lazy_val = lazy_val.host
818-
return lazy_val.get_id()
886+
# retrieve the correct id
887+
all_case_funcs = get_current_cases(request_or_item)
888+
return all_case_funcs[argnames[0]][0]
819889

820890

821891
# Below is the beginning of a switch from our code scanning tool above to the same one than pytest.

pytest_cases/plugin.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
# # we will need to clean the empty ids explicitly in the plugin :'(
3838
from .fixture_parametrize_plus import remove_empty_ids
3939

40+
from .case_parametrizer_new import get_current_cases
41+
4042

4143
_DEBUG = False
4244

@@ -1459,3 +1461,18 @@ def pytest_collection_modifyitems(session, config, items): # noqa
14591461
else:
14601462
# do nothing
14611463
yield
1464+
1465+
1466+
@pytest.fixture
1467+
def current_cases(request):
1468+
"""
1469+
A fixture containing `get_current_cases(request)`
1470+
1471+
This is a dictionary of {argname: (actual_id, case_function)} for a given `pytest` item. The `actual_id`
1472+
might differ from the case_id defined on the case, since it might be overridden through pytest cusomtization.
1473+
To get more information on the case function, you can use `get_case_id(f)`, `get_case_marks(f)`, `get_case_tags(f)`.
1474+
1475+
You can also use `matches_tag_query` to check if a case function matches some expectations either concerning its id
1476+
or its tags. See https://smarie.github.io/python-pytest-cases/#filters-and-tags
1477+
"""
1478+
return get_current_cases(request)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Authors: Sylvain MARIE <[email protected]>
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+
from pytest_cases import parametrize_with_cases
6+
7+
8+
def case_a():
9+
return 1, 2
10+
11+
def tuplecase_a():
12+
return 1, 2
13+
14+
def fixturecase_a(request):
15+
return 1, 2
16+
17+
def fixturetuplecase_a(request):
18+
return 1, 2
19+
20+
21+
@parametrize_with_cases("f1,f2", cases=fixturetuplecase_a)
22+
@parametrize_with_cases("f", cases=fixturecase_a)
23+
@parametrize_with_cases("t1,t2", cases=tuplecase_a)
24+
@parametrize_with_cases("a", cases=case_a)
25+
def test_a(a, t1, t2, f, f1, f2, current_cases):
26+
assert current_cases == {
27+
"a": ("a", case_a),
28+
"t1": ("tuplecase_a", tuplecase_a),
29+
"t2": ("tuplecase_a", tuplecase_a),
30+
"f": ("fixturecase_a", fixturecase_a),
31+
"f1": ("fixturetuplecase_a", fixturetuplecase_a),
32+
"f2": ("fixturetuplecase_a", fixturetuplecase_a),
33+
}

0 commit comments

Comments
 (0)