Fixed class-scoped pytest fixtures that were defined as instance methods
using ``self``, which is deprecated as of pytest 9.1 and will be removed in
pytest 10. Fixtures are now decorated with a compatibility ``@classmethod``
decorator and use ``cls`` as the first parameter.
In the 2.0 branch, the approach is considerably more complicated
as python 3.7, 3.8, 3.9 doesn't work with this pattern; since the
warning appears in pytest 9.1 and not 9.0, which itself is only
python 3.10+, we use a conditional classmethod wrapper.
in the 2.1 branch, we can just use straight `@classmethod`.
Fixes: #13392
Change-Id: I866de54e22569c9d55eb97dce165670994ec4a57
(cherry picked from commit
cd7eeb34d3741c94b2f9476aec7f3386132a71d5)
--- /dev/null
+.. change::
+ :tags: bug, tests
+ :tickets: 13392
+
+ Fixed class-scoped pytest fixtures that were defined as instance methods
+ using ``self``, which is deprecated as of pytest 9.1 and will be removed in
+ pytest 10. Fixtures are now decorated with a compatibility ``@classmethod``
+ decorator and use ``cls`` as the first parameter.
from .config import combinations_list
from .config import db
from .config import fixture
+from .config import fixture_classmethod
from .config import requirements as requires
from .config import skip_test
from .config import Variation
def async_test(self, fn):
return fn
+ def fixture_classmethod(self, fn):
+ return classmethod(fn)
+
# default fixture functions; these are replaced by plugin_base when
# pytest runs
_fixture_functions = _NullFixtureFunctions()
def async_test(fn):
return _fixture_functions.async_test(fn)
+
+
+def fixture_classmethod(fn):
+ return _fixture_functions.fixture_classmethod(fn)
yield from self._cachedir()
@config.fixture(scope="class")
- def cachedir(self):
- yield from self._cachedir()
+ @config.fixture_classmethod
+ def cachedir(cls):
+ yield from cls._cachedir()
- def _cachedir(self):
+ @config.fixture_classmethod
+ def _cachedir(cls):
# as of mypy 0.971 i think we need to keep mypy_path empty
mypy_path = ""
classes: Any = None
@config.fixture(autouse=True, scope="class")
- def _setup_tables_test_class(self):
- cls = self.__class__
+ @config.fixture_classmethod
+ def _setup_tables_test_class(cls):
cls._init_class()
if cls.classes is None:
sequences = None
@config.fixture(autouse=True, scope="class")
- def _setup_tables_test_class(self):
- cls = self.__class__
+ @config.fixture_classmethod
+ def _setup_tables_test_class(cls):
cls._init_class()
cls._setup_once_tables()
def add_to_marker(self):
raise NotImplementedError()
+ @abc.abstractmethod
+ def fixture_classmethod(self, fn) -> Any:
+ raise NotImplementedError()
+
_fixture_fn_class = None
import argparse
import collections
from functools import update_wrapper
+from functools import wraps
import inspect
import itertools
import operator
class PytestFixtureFunctions(plugin_base.FixtureFunctions):
+
+ def fixture_classmethod(self, fn):
+ """a conditional `@classmethod` decorator that we use only on py3.10
+ on forward, for compatibility with pytest 9.1+."""
+
+ if pytest.version_tuple >= (9, 1):
+ return classmethod(fn)
+ else:
+ if inspect.isgeneratorfunction(fn):
+
+ @wraps(fn)
+ def wrap(self, *args, **kw):
+ yield from fn(self.__class__, *args, **kw)
+
+ else:
+
+ @wraps(fn)
+ def wrap(self, *args, **kw):
+ return fn(self.__class__, *args, **kw)
+
+ return wrap
+
def skip_test_exception(self, *arg, **kw):
return pytest.skip.Exception(*arg, **kw)
# now apply wrappers to the function, including fixture itself
def wrap(fn):
+ is_classmethod = isinstance(fn, classmethod)
+ if is_classmethod:
+ fn = fn.__func__
+
if config.any_async:
fn = asyncio._maybe_async_wrapper(fn)
# other wrappers may be added here
+ if is_classmethod:
+ fn = classmethod(fn)
+
# now apply FixtureFunctionMarker
fn = fixture(fn)
__requires__ = ("cpython", "python_profiling_backend")
@testing.fixture(scope="class")
- def mapping_fixture(self):
+ @testing.fixture_classmethod
+ def mapping_fixture(cls):
# note in order to work nicely with "fixture" we are emerging
# a whole new model of setup/teardown, since pytest "fixture"
# sort of purposely works badly with setup/teardown
class _MutableNoHashFixture:
@testing.fixture(autouse=True, scope="class")
- def set_class(self):
+ @testing.fixture_classmethod
+ def set_class(cls):
global Foo
_replace_foo = Foo