pytest-lazy-fixture
pytest-lazy-fixture copied to clipboard
Raise error with `pytest==8.0.0`
Hi! pytest-lazy-fixture
doesn't work with pytest==8.0.0
AttributeError: 'CallSpec2' object has no attribute 'funcargs'
Since the main branch of this repo hasn't seen an update in two years, I've been looking for an alternative. In my case, I was using something like this:
# In conftest.py:
@pytest.fixture
def test_api_sqlite_mp(test_sqlite_mp):
return Platform(_backend=RestTestBackend(test_sqlite_mp.backend))
@pytest.fixture
def test_api_pgsql_mp(test_pgsql_mp):
return Platform(_backend=RestTestBackend(test_pgsql_mp.backend))
# In another file:
api_platforms = pytest.mark.parametrize(
"test_mp",
[
pytest.lazy_fixture("test_api_sqlite_mp"),
pytest.lazy_fixture("test_api_pgsql_mp"),
],
)
# And finally, for the function:
@api_platforms
def test_index_model(test_mp):
...
So following suggestions on StackOverflow and here, I changed that to
# In conftest.py (everything stays the same):
@pytest.fixture
def test_api_sqlite_mp(test_sqlite_mp):
return Platform(_backend=RestTestBackend(test_sqlite_mp.backend))
@pytest.fixture
def test_api_pgsql_mp(test_pgsql_mp):
return Platform(_backend=RestTestBackend(test_pgsql_mp.backend))
# In another file:
api_platforms = pytest.mark.parametrize(
"test_mp",
[
"test_api_sqlite_mp",
"test_api_pgsql_mp",
],
)
# And finally, for the function:
@api_platforms
def test_index_model(test_mp, request):
test_mp = request.getfixturevalue(test_mp)
...
After uninstalling pytest-lazy-fixture, my tests are running just fine again. Hope this helps :)
Hi All,
As mentioned by @glatterf42, it looks like this issue might never get fixed. So what I did was to come out with my own local pytest-lazy-fixture
plugin like this:
import dataclasses
import typing
import pytest
@dataclasses.dataclass
class LazyFixture:
"""Lazy fixture dataclass."""
name: str
def lazy_fixture(name: str) -> LazyFixture:
"""Mark a fixture as lazy."""
return LazyFixture(name)
def is_lazy_fixture(value: object) -> bool:
"""Check whether a value is a lazy fixture."""
return isinstance(value, LazyFixture)
def pytest_make_parametrize_id(
config: pytest.Config,
val: object,
argname: str,
) -> str | None:
"""Inject lazy fixture parametrized id.
Reference:
- https://bit.ly/48Off6r
Args:
config (pytest.Config): pytest configuration.
value (object): fixture value.
argname (str): automatic parameter name.
Returns:
str: new parameter id.
"""
if is_lazy_fixture(val):
return typing.cast(LazyFixture, val).name
return None
@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(
fixturedef: pytest.FixtureDef,
request: pytest.FixtureRequest,
) -> object | None:
"""Lazy fixture setup hook.
This hook will never take over a fixture setup but just simply will
try to resolve recursively any lazy fixture found in request.param.
Reference:
- https://bit.ly/3SyvsXJ
Args:
fixturedef (pytest.FixtureDef): fixture definition object.
request (pytest.FixtureRequest): fixture request object.
Returns:
object | None: fixture value or None otherwise.
"""
if hasattr(request, "param") and request.param:
request.param = _resolve_lazy_fixture(request.param, request)
return None
def _resolve_lazy_fixture(__val: object, request: pytest.FixtureRequest) -> object:
"""Lazy fixture resolver.
Args:
__val (object): fixture value object.
request (pytest.FixtureRequest): pytest fixture request object.
Returns:
object: resolved fixture value.
"""
if isinstance(__val, list | tuple):
return tuple(_resolve_lazy_fixture(v, request) for v in __val)
if isinstance(__val, typing.Mapping):
return {k: _resolve_lazy_fixture(v, request) for k, v in __val.items()}
if not is_lazy_fixture(__val):
return __val
lazy_obj = typing.cast(LazyFixture, __val)
return request.getfixturevalue(lazy_obj.name)
By now I am simply including this into my root conftest.py
.
For the record, the minimal set of changes to make things work again doesn't seem to be that massive. This works for me, passing pytest-lazy-fixture's test set (not running tox, just running Python 3.10 with pytest 8.0.0):
diff --git a/pytest_lazyfixture.py b/pytest_lazyfixture.py
index abf5db5..df83ce7 100644
--- a/pytest_lazyfixture.py
+++ b/pytest_lazyfixture.py
@@ -71,14 +71,13 @@ def pytest_make_parametrize_id(config, val, argname):
def pytest_generate_tests(metafunc):
yield
- normalize_metafunc_calls(metafunc, 'funcargs')
- normalize_metafunc_calls(metafunc, 'params')
+ normalize_metafunc_calls(metafunc)
-def normalize_metafunc_calls(metafunc, valtype, used_keys=None):
+def normalize_metafunc_calls(metafunc, used_keys=None):
newcalls = []
for callspec in metafunc._calls:
- calls = normalize_call(callspec, metafunc, valtype, used_keys)
+ calls = normalize_call(callspec, metafunc, used_keys)
newcalls.extend(calls)
metafunc._calls = newcalls
@@ -98,17 +97,21 @@ def copy_metafunc(metafunc):
return copied
-def normalize_call(callspec, metafunc, valtype, used_keys):
+def normalize_call(callspec, metafunc, used_keys):
fm = metafunc.config.pluginmanager.get_plugin('funcmanage')
used_keys = used_keys or set()
- valtype_keys = set(getattr(callspec, valtype).keys()) - used_keys
+ keys = set(callspec.params.keys()) - used_keys
+ print(used_keys, keys)
- for arg in valtype_keys:
- val = getattr(callspec, valtype)[arg]
+ for arg in keys:
+ val = callspec.params[arg]
if is_lazy_fixture(val):
try:
- _, fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], metafunc.definition.parent)
+ if pytest.version_tuple >= (8, 0, 0):
+ fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure(metafunc.definition.parent, [val.name], {})
+ else:
+ _, fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], metafunc.definition.parent)
except ValueError:
# 3.6.0 <= pytest < 3.7.0; `FixtureManager.getfixtureclosure` returns 2 values
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], metafunc.definition.parent)
@@ -117,14 +120,14 @@ def normalize_call(callspec, metafunc, valtype, used_keys):
fixturenames_closure, arg2fixturedefs = fm.getfixtureclosure([val.name], current_node)
extra_fixturenames = [fname for fname in fixturenames_closure
- if fname not in callspec.params and fname not in callspec.funcargs]
+ if fname not in callspec.params]# and fname not in callspec.funcargs]
newmetafunc = copy_metafunc(metafunc)
newmetafunc.fixturenames = extra_fixturenames
newmetafunc._arg2fixturedefs.update(arg2fixturedefs)
newmetafunc._calls = [callspec]
fm.pytest_generate_tests(newmetafunc)
- normalize_metafunc_calls(newmetafunc, valtype, used_keys | set([arg]))
+ normalize_metafunc_calls(newmetafunc, used_keys | set([arg]))
return newmetafunc._calls
used_keys.add(arg)
But the bigger question is of course how much more overdue maintenance is waiting to happen, and whether whether @TvoroG would be interested to keep maintaining or to transfer maintenance to someone else (cfr #63).
I didn't look at the code in detail, but if the only problem is the call to getfixtureclosure
, https://github.com/pytest-dev/pytest/pull/11888 will fix the problem.
I still get an error with https://github.com/pytest-dev/pytest/pull/11888:
______________________ ERROR collecting tests/test_prettytable.py _______________________
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pluggy/_hooks.py:501: in __call__
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pluggy/_manager.py:119: in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../pytest/src/_pytest/python.py:274: in pytest_pycollect_makeitem
return list(collector._genfunctions(name, obj))
../pytest/src/_pytest/python.py:489: in _genfunctions
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pluggy/_hooks.py:562: in call_extra
return self._hookexec(self.name, hookimpls, kwargs, firstresult)
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pluggy/_manager.py:119: in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pytest_lazyfixture.py:74: in pytest_generate_tests
normalize_metafunc_calls(metafunc, 'funcargs')
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pytest_lazyfixture.py:81: in normalize_metafunc_calls
calls = normalize_call(callspec, metafunc, valtype, used_keys)
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/pytest_lazyfixture.py:105: in normalize_call
valtype_keys = set(getattr(callspec, valtype).keys()) - used_keys
E AttributeError: 'CallSpec2' object has no attribute 'funcargs'
I didn't look at the code in detail, but if the only problem is the call to
getfixtureclosure
, pytest-dev/pytest#11888 will fix the problem.
I believe the order of arguments also changed, or am I misreading the 3 different versions in pytest-lazy-fixutre?
I still get an error with pytest-dev/pytest#11888:
And yes, the other fix is that CallSpec2.funcargs
was merged into CallSpec2.params
? At least, stopping to access funcargs
and only accessing params
did the trick.
(So yes, actually my patch up there is not backwards compatible, right now.)
I was able to easily replace this plugin with https://github.com/dev-petrov/pytest-lazy-fixtures:
- https://github.com/jazzband/prettytable/pull/279
I mainly used this package because it alowed me to use parametrized fixtures in parametrized tests, producing a product of test cases. I have tested @glatterf42 's and @PrieJos 's solution but both produced errors
Failed: The requested fixture has no parameter defined for test
when I used them with parametrized fixtures.
Does anyone have a solution for this?
I mainly used this package because it alowed me to use parametrized fixtures in parametrized tests, producing a product of test cases. I have tested @glatterf42 's and @PrieJos 's solution but both produced errors
Failed: The requested fixture has no parameter defined for test
when I used them with parametrized fixtures. Does anyone have a solution for this?
I've already raised an issue about this. I'm sticking with pytest 7.x in affected projects until it's fixed.
@harsanyidani, @agronholm: without knowing more about your error messages, I still found some possibly related issues. Please check:
- https://github.com/pytest-dev/pytest/issues/11075 for a possible solution
- https://stackoverflow.com/questions/58319619/pytest-request-param-of-fixture-to-be-used-in-tests for another possible solution (though the error message might be different)
- https://github.com/pytest-dev/pytest/issues/4666 for some background for this error
What do you mean, without knowing? I posted trivial repro instructions on the linked issue, and I'm getting the same error as @harsanyidani. On pytest 7.x, all this works perfectly with this project but not with the new one.
While @harsanyidani provided a crucial part of the error they are getting, it's by no means a full traceback (as is provided in the first related issue I linked above). The issue you created added some context, so thank you for that, but it's in another repository and I don't have the time right now to install this other code and debug it. Please keep in mind that I'm not maintaining this repo, I was just trying to help with a quick search.
There is no traceback. This was the output I got when trying to run that trivial repro test module with pytest 7:
collected 2 items
tests/test_foo.py::test_foo[service1] ERROR [ 50%]
tests/test_foo.py::test_foo[service2] ERROR [100%]
================================================================================================================================================================= ERRORS ==================================================================================================================================================================
__________________________________________________________________________________________________________________________________________________ ERROR at setup of test_foo[service1] ___________________________________________________________________________________________________________________________________________________
The requested fixture has no parameter defined for test:
tests/test_foo.py::test_foo[service1]
Requested fixture 'fixture1' defined in:
tests/test_foo.py:7
Requested here:
venv38/lib64/python3.8/site-packages/_pytest/fixtures.py:693
__________________________________________________________________________________________________________________________________________________ ERROR at setup of test_foo[service2] ___________________________________________________________________________________________________________________________________________________
The requested fixture has no parameter defined for test:
tests/test_foo.py::test_foo[service2]
Requested fixture 'fixture1' defined in:
tests/test_foo.py:7
Requested here:
venv38/lib64/python3.8/site-packages/_pytest/fixtures.py:693
============================================================================================================================================================ 2 errors in 0.04s ============================================================================================================================================================
Thanks, and did you try giving def fixture2(requests, service1, service2)
for example? Just guessing here based on the links above.
That's hardly equivalent with what I had before. That would activate both service1 and service2 at the same time which is not what I want.
There is no traceback. This was the output I got when trying to run that trivial repro test module with pytest 7:
collected 2 items tests/test_foo.py::test_foo[service1] ERROR [ 50%] tests/test_foo.py::test_foo[service2] ERROR [100%] ================================================================================================================================================================= ERRORS ================================================================================================================================================================== __________________________________________________________________________________________________________________________________________________ ERROR at setup of test_foo[service1] ___________________________________________________________________________________________________________________________________________________ The requested fixture has no parameter defined for test: tests/test_foo.py::test_foo[service1] Requested fixture 'fixture1' defined in: tests/test_foo.py:7 Requested here: venv38/lib64/python3.8/site-packages/_pytest/fixtures.py:693 __________________________________________________________________________________________________________________________________________________ ERROR at setup of test_foo[service2] ___________________________________________________________________________________________________________________________________________________ The requested fixture has no parameter defined for test: tests/test_foo.py::test_foo[service2] Requested fixture 'fixture1' defined in: tests/test_foo.py:7 Requested here: venv38/lib64/python3.8/site-packages/_pytest/fixtures.py:693 ============================================================================================================================================================ 2 errors in 0.04s ============================================================================================================================================================
Yes, this is exactly what I'm getting.
Alright then, sorry I can't be of more help right now.
Yeah, the only way forward is for the author of the new library to look at this project and fill in the missing functionality in theirs.
But seriously though, this should be a built-in feature in pytest itself.
There might be one more thing you can do: show the pytest devs in their existing issue to integrate pytest-lazy-fixture in core pytest that you're interested in that :)
The package seems to be fully replaced with lazy-fixtures which seems to have all the features we get used to.
The package seems to be fully replaced with lazy-fixtures which seems to have all the features we get used to.
It is .... in Debian at least. All reverse-dependencies have been sorted out & pytest-lazy-fixture has been removed. Patches have been sent to upstream projects.