Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Changelog

### 3.6.7 In Progress - Minor improvements
### 3.6.7 - Minor improvements and preparing for pytest 7

- Improved error message when a case function nested in a class has no `self` argument and is not static. Fixes [#243](https://github.com/smarie/python-pytest-cases/issues/243)
- Added support for the new Scopes enum in pytest 7. Fixed [#241](https://github.com/smarie/python-pytest-cases/issues/241)
- Fixed `__version__` in development mode.

### 3.6.6 - Layout change

Expand Down
2 changes: 1 addition & 1 deletion src/pytest_cases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# use setuptools_scm to get the current version from src using git
from setuptools_scm import get_version as _gv
from os import path as _path
__version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir))
__version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir, _path.pardir))


AUTO2 = AUTO
Expand Down
2 changes: 1 addition & 1 deletion src/pytest_cases/case_parametrizer_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ def _of_interest(x): # noqa
# ignore any error here, this is optional.
pass
else:
if len(s.parameters) < 1:
if len(s.parameters) < 1 or (tuple(s.parameters.keys())[0] != "self"):
raise TypeError("case method is missing 'self' argument but is not static: %s" % m)
# partialize the function to get one without the 'self' argument
new_m = functools.partial(m, cls())
Expand Down
33 changes: 26 additions & 7 deletions src/pytest_cases/common_pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,18 +521,37 @@ def get_pytest_nodeid(metafunc):


try:
from _pytest.fixtures import scopes as pt_scopes
# pytest 7+ : scopes is an enum
from _pytest.scope import Scope

def get_pytest_function_scopeval():
return Scope.Function

def has_function_scope(fixdef):
return fixdef._scope is Scope.Function

def set_callspec_arg_scope_to_function(callspec, arg_name):
callspec._arg2scope[arg_name] = Scope.Function

except ImportError:
# pytest 2
from _pytest.python import scopes as pt_scopes, Metafunc # noqa
try:
# pytest 3+
from _pytest.fixtures import scopes as pt_scopes
except ImportError:
# pytest 2
from _pytest.python import scopes as pt_scopes

# def get_pytest_scopenum(scope_str):
# return pt_scopes.index(scope_str)

def get_pytest_scopenum(scope_str):
return pt_scopes.index(scope_str)
def get_pytest_function_scopeval():
return pt_scopes.index("function")

def has_function_scope(fixdef):
return fixdef.scopenum == get_pytest_function_scopeval()

def get_pytest_function_scopenum():
return pt_scopes.index("function")
def set_callspec_arg_scope_to_function(callspec, arg_name):
callspec._arg2scopenum[arg_name] = get_pytest_function_scopeval() # noqa


from _pytest.python import _idval # noqa
Expand Down
1 change: 1 addition & 0 deletions src/pytest_cases/common_pytest_marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
PYTEST54_OR_GREATER = PYTEST_VERSION >= LooseVersion('5.4.0')
PYTEST421_OR_GREATER = PYTEST_VERSION >= LooseVersion('4.2.1')
PYTEST6_OR_GREATER = PYTEST_VERSION >= LooseVersion('6.0.0')
PYTEST7_OR_GREATER = PYTEST_VERSION >= LooseVersion('7.0.0')


def get_param_argnames_as_list(argnames):
Expand Down
37 changes: 23 additions & 14 deletions src/pytest_cases/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@

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
from .common_pytest import get_pytest_nodeid, get_pytest_function_scopenum, is_function_node, get_param_names, \
get_param_argnames_as_list
from .common_pytest_marks import PYTEST35_OR_GREATER, PYTEST46_OR_GREATER, PYTEST37_OR_GREATER, PYTEST7_OR_GREATER
from .common_pytest import get_pytest_nodeid, get_pytest_function_scopeval, is_function_node, get_param_names, \
get_param_argnames_as_list, has_function_scope, set_callspec_arg_scope_to_function

from .fixture_core1_unions import NOT_USED, USED, is_fixture_union_params, UnionFixtureAlternative

Expand Down Expand Up @@ -187,12 +187,22 @@ def get_all_fixture_defs(self, drop_fake_fixtures=True, try_to_sort=True):
items = self.gen_all_fixture_defs(drop_fake_fixtures=drop_fake_fixtures)

# sort by scope as in pytest fixture closure creator (pytest did not do it in early versions, align with this)
if try_to_sort and PYTEST35_OR_GREATER:
f_scope = get_pytest_function_scopenum()
def sort_by_scope(kv_pair): # noqa
fixture_name, fixture_defs = kv_pair
return fixture_defs[-1].scopenum if fixture_defs is not None else f_scope
items = sorted(list(items), key=sort_by_scope)
if try_to_sort:
if PYTEST7_OR_GREATER:
# Scope is an enum, values are in reversed order, and the field is _scope
f_scope = get_pytest_function_scopeval()
def sort_by_scope(kv_pair):
fixture_name, fixture_defs = kv_pair
return fixture_defs[-1]._scope if fixture_defs is not None else f_scope
items = sorted(list(items), key=sort_by_scope, reverse=True)

elif PYTEST35_OR_GREATER:
# scopes is a list, values are indices in the list, and the field is scopenum
f_scope = get_pytest_function_scopeval()
def sort_by_scope(kv_pair): # noqa
fixture_name, fixture_defs = kv_pair
return fixture_defs[-1].scopenum if fixture_defs is not None else f_scope
items = sorted(list(items), key=sort_by_scope)

return OrderedDict(items)

Expand Down Expand Up @@ -562,7 +572,7 @@ def _update_fixture_defs(self):

# # also sort all partitions (note that we cannot rely on the order in all_fixture_defs when scopes are same!)
# if LooseVersion(pytest.__version__) >= LooseVersion('3.5.0'):
# f_scope = get_pytest_function_scopenum()
# f_scope = get_pytest_function_scopeval()
# for p in self.partitions:
# def sort_by_scope2(fixture_name): # noqa
# fixture_defs = all_fixture_defs[fixture_name]
Expand Down Expand Up @@ -1031,14 +1041,13 @@ def _cleanup_calls_list(metafunc,
# create ref lists of fixtures per scope
_not_always_used_func_scoped = []
# _not_always_used_other_scoped = []
_function_scope_num = get_pytest_function_scopenum()
for fixture_name in fix_closure_tree.get_not_always_used():
try:
fixdef = metafunc._arg2fixturedefs[fixture_name] # noqa
except KeyError:
continue # dont raise any error here and let pytest say "not found" later
else:
if fixdef[-1].scopenum == _function_scope_num:
if has_function_scope(fixdef[-1]):
_not_always_used_func_scoped.append(fixture_name)
# else:
# _not_always_used_other_scoped.append(fixture_name)
Expand Down Expand Up @@ -1078,12 +1087,12 @@ def _cleanup_calls_list(metafunc,
# explicitly add it as discarded by creating a parameter value for it.
c.params[fixture_name] = NOT_USED
c.indices[fixture_name] = 1
c._arg2scopenum[fixture_name] = _function_scope_num # get_pytest_scopenum(fixdef[-1].scope) # noqa
set_callspec_arg_scope_to_function(c, fixture_name)
else:
# explicitly add it as active
c.params[fixture_name] = USED
c.indices[fixture_name] = 0
c._arg2scopenum[fixture_name] = _function_scope_num # get_pytest_scopenum(fixdef[-1].scope) # noqa
set_callspec_arg_scope_to_function(c, fixture_name)

# finally, if there are some session or module-scoped fixtures that
# are used in *none* of the calls, they could be deactivated too
Expand Down
20 changes: 0 additions & 20 deletions tests/cases/issues/test_issue_243.py

This file was deleted.

36 changes: 36 additions & 0 deletions tests/cases/issues/test_py35_issue_243.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest

from pytest_cases import parametrize_with_cases, fixture


def test_missing_self():
class MyCases:
def case_forgot_self() -> int:
return 456

with pytest.raises(TypeError) as exc_info:
@parametrize_with_cases(argnames="expected", cases=MyCases)
def test_foo(expected):
pass

assert str(exc_info.value) == ("case method is missing 'self' argument but is not static: %s"
% MyCases.case_forgot_self)


@fixture
def a():
return


def test_missing_self_params():
class MyCases:
def case_fix_forgot_self(a) -> int:
return a

with pytest.raises(TypeError) as exc_info:
@parametrize_with_cases(argnames="expected", cases=MyCases)
def test_foo(expected):
pass

assert str(exc_info.value) == ("case method is missing 'self' argument but is not static: %s"
% MyCases.case_fix_forgot_self)