Deprecate self in class-scoped fixtures on base classes (#10819) (#14011)#14071
Deprecate self in class-scoped fixtures on base classes (#10819) (#14011)#14071yastcher wants to merge 2 commits intopytest-dev:mainfrom
self in class-scoped fixtures on base classes (#10819) (#14011)#14071Conversation
yastcher
commented
Dec 28, 2025
- closes Inherit fixture from baseclass with class scope fails #14011
- closes Fixture with scope=class and autouse=True is not executed from mother's test class #10819
6c5967f to
9772d0c
Compare
9772d0c to
acd357c
Compare
acd357c to
ff1759c
Compare
| pass | ||
|
|
||
| @pytest.fixture(scope="class") | ||
| def fix(self) -> typing.Generator[None]: |
There was a problem hiding this comment.
reminds me that we need to ensure that class scope fixtures do not actually ever get a instance - class scope is above instances and should get a class
There was a problem hiding this comment.
Addressed: added _ClassScopeInstance proxy that redirects attribute writes to the class, so class-scoped fixtures never work with actual test instances
There was a problem hiding this comment.
Implemented _ClassScopeInstance proxy that redirects attribute writes to the class, so class-scoped fixtures work correctly without actual test instances.
However, I noticed your comment in #10819 about deprecating non-classmethod class-scoped fixtures. If that's the preferred direction, this fix might be unnecessary — we could add a deprecation warning instead and guide users toward @ classmethod fixtures.
Should I proceed with the proxy approach, or switch to deprecation warning?
There was a problem hiding this comment.
technically speaking its a bug to even try to pass a instance to a class scope fixture as no instance is available in that context
that a instance is actually avaliable is a bug an a artifact of how setupstate and scope initially developed
before there where fixtures we had the pytest_funcarg__ARGNAME hooks and no scopes at all
There was a problem hiding this comment.
Got it. So the fix is correct — class-scoped fixtures should never receive an actual instance. The _ClassScopeInstance proxy ensures they work with the class directly, which is the intended behavior
There was a problem hiding this comment.
We should error in future and warn right now
Instance methods are always wrong
Class scope requires classmethods
There was a problem hiding this comment.
Understood. Will remove the proxy and add deprecation warning instead.
Proposed implementation:
In src/_pytest/deprecated.py:
CLASS_FIXTURE_INSTANCE_METHOD = PytestRemovedIn10Warning(
"Class-scoped fixture defined as instance method is deprecated.\n"
"Use @classmethod decorator instead.\n"
"See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method"
)Then warn in resolve_fixture_function when class-scoped fixture is an instance method.
Does this look right?
There was a problem hiding this comment.
the deprecation message should be very aggressive about the fact that any changes to the instance would in fact not transfer to subsequent instances
There was a problem hiding this comment.
CLASS_FIXTURE_INSTANCE_METHOD = PytestRemovedIn10Warning(
"Class-scoped fixture defined as instance method is deprecated.\n"
"Instance attributes set in this fixture will NOT be visible to test methods,\n"
"as each test gets a new instance while the fixture runs only once per class.\n"
"Use @classmethod decorator and set attributes on cls instead.\n"
"See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method"
)?
98b0e10 to
395b55c
Compare
d8935c9 to
959c305
Compare
| "Instance attributes set in this fixture will NOT be visible to test methods,\n" | ||
| "as each test gets a new instance while the fixture runs only once per class.\n" | ||
| "Use @classmethod decorator and set attributes on cls instead.\n" | ||
| "See https://docs.pytest.org/en/stable/deprecations.html#class-scoped-fixture-as-instance-method" |
There was a problem hiding this comment.
We need to introduce a new section in deprecations.rst explaning this deprecation, including examples of how to update the code.
There was a problem hiding this comment.
.. _class-scoped-fixture-as-instance-method:
Class-scoped fixture as instance method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 9.1
Defining a class-scoped fixture as an instance method (without ``@classmethod``) is deprecated
and will be removed in pytest 10.0.
When a class-scoped fixture is defined as an instance method, any attributes set on ``self``
will not be visible to test methods. This happens because pytest creates a new instance of the
test class for each test method, while the fixture runs only once per class on a different instance.
**Before** (deprecated):
.. code-block:: python
class TestExample:
@pytest.fixture(scope="class")
def setup_data(self):
self.data = [1, 2, 3] # This won't be visible to tests!
def test_something(self, setup_data):
assert self.data == [1, 2, 3] # AttributeError: 'TestExample' object has no attribute 'data'
**After** (recommended):
.. code-block:: python
class TestExample:
@pytest.fixture(scope="class")
@classmethod
def setup_data(cls):
cls.data = [1, 2, 3]
def test_something(self, setup_data):
assert self.data == [1, 2, 3] # Works correctly
Using ``@classmethod`` ensures attributes are set on the class itself, making them accessible
to all test methods.
| @@ -0,0 +1,92 @@ | |||
| from __future__ import annotations | |||
There was a problem hiding this comment.
Not sure why we need this test module? I feel the deprecation and its test is enough for this change.
There was a problem hiding this comment.
Removed. This test file was created for the initial approach (fixing the behavior), but became redundant once we decided to deprecate instead. The deprecation warning is already covered by the test in deprecated_test.py
959c305 to
1100ad2
Compare
5cafff1 to
0f3894c
Compare
|
Could you rephrase the PR title? It's rather vague. |
0f3894c to
df2e44d
Compare
self in class-scoped fixtures on base classes (#10819) (#14011)
df2e44d to
cdc35a9
Compare