Skip to content

Commit b2e0d26

Browse files
author
Anis Da Silva Campos
committed
Revert "fix fixture checker"
This reverts commit da69357.
1 parent da69357 commit b2e0d26

File tree

7 files changed

+139
-142
lines changed

7 files changed

+139
-142
lines changed

pylint_pytest/__init__.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
from pylint.checkers.variables import VariablesChecker
2-
from pylint.lint import PyLinter
1+
import glob
2+
import importlib
3+
import inspect
4+
import os
35

4-
from .checkers.class_attr_loader import ClassAttrLoader
5-
from .checkers.fixture import FixtureChecker
6-
from .checkers.variables import CustomVariablesChecker
6+
from .checkers import BasePytestChecker
77

88

9-
def register(linter: PyLinter) -> None:
10-
"""Register the checker classes"""
11-
remove_original_variables_checker(linter)
12-
linter.register_checker(CustomVariablesChecker(linter))
13-
linter.register_checker(FixtureChecker(linter))
14-
linter.register_checker(ClassAttrLoader(linter))
9+
def register(linter):
10+
"""auto discover pylint checker classes"""
11+
dirname = os.path.dirname(__file__)
12+
for module in glob.glob(os.path.join(dirname, "checkers", "*.py")):
13+
# trim file extension
14+
module = os.path.splitext(module)[0]
1515

16+
# use relative path only
17+
module = module.replace(dirname, "", 1)
1618

17-
def remove_original_variables_checker(linter: PyLinter) -> None:
18-
"""We need to remove VariablesChecker before registering CustomVariablesChecker"""
19-
variable_checkers = linter._checkers[VariablesChecker.name] # pylint: disable=protected-access
20-
for checker in variable_checkers:
21-
if isinstance(checker, VariablesChecker):
22-
variable_checkers.remove(checker)
19+
# translate file path into module import path
20+
module = module.replace(os.sep, ".")
21+
22+
checker = importlib.import_module(module, package=os.path.basename(dirname))
23+
for attr_name in dir(checker):
24+
attr_val = getattr(checker, attr_name)
25+
if (
26+
attr_val != BasePytestChecker
27+
and inspect.isclass(attr_val)
28+
and issubclass(attr_val, BasePytestChecker)
29+
):
30+
linter.register_checker(attr_val(linter))

pylint_pytest/checkers/fixture.py

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77

88
import astroid
99
import pytest
10+
from pylint.checkers.variables import VariablesChecker
1011

1112
from ..utils import (
1213
_can_use_fixture,
1314
_is_pytest_fixture,
1415
_is_pytest_mark,
1516
_is_pytest_mark_usefixtures,
17+
_is_same_module,
1618
)
1719
from . import BasePytestChecker
18-
from .types import FixtureDict
20+
from .types import FixtureDict, replacement_add_message
1921

2022
# TODO: support pytest python_files configuration
2123
FILE_NAME_PATTERNS: tuple[str, ...] = ("test_*.py", "*_test.py")
@@ -73,28 +75,38 @@ class FixtureChecker(BasePytestChecker):
7375
}
7476

7577
# Store all fixtures discovered by pytest session
76-
pytest_fixtures: FixtureDict = {}
78+
_pytest_fixtures: FixtureDict = {}
7779
# Stores all used function arguments
78-
invoked_with_func_args: set[str] = set()
80+
_invoked_with_func_args: set[str] = set()
7981
# Stores all invoked fixtures through @pytest.mark.usefixture(...)
80-
invoked_with_usefixtures: set[str] = set()
82+
_invoked_with_usefixtures: set[str] = set()
83+
_original_add_message = replacement_add_message
84+
85+
def open(self):
86+
# patch VariablesChecker.add_message
87+
FixtureChecker._original_add_message = VariablesChecker.add_message
88+
VariablesChecker.add_message = FixtureChecker.patch_add_message
8189

8290
def close(self):
8391
"""restore & reset class attr for testing"""
92+
# restore add_message
93+
VariablesChecker.add_message = FixtureChecker._original_add_message
94+
FixtureChecker._original_add_message = replacement_add_message
95+
8496
# reset fixture info storage
85-
FixtureChecker.pytest_fixtures = {}
86-
FixtureChecker.invoked_with_func_args = set()
87-
FixtureChecker.invoked_with_usefixtures = set()
97+
FixtureChecker._pytest_fixtures = {}
98+
FixtureChecker._invoked_with_func_args = set()
99+
FixtureChecker._invoked_with_usefixtures = set()
88100

89101
def visit_module(self, node):
90102
"""
91103
- only run once per module
92104
- invoke pytest session to collect available fixtures
93105
- create containers for the module to store args and fixtures
94106
"""
95-
FixtureChecker.pytest_fixtures = {}
96-
FixtureChecker.invoked_with_func_args = set()
97-
FixtureChecker.invoked_with_usefixtures = set()
107+
FixtureChecker._pytest_fixtures = {}
108+
FixtureChecker._invoked_with_func_args = set()
109+
FixtureChecker._invoked_with_usefixtures = set()
98110

99111
is_test_module = False
100112
for pattern in FILE_NAME_PATTERNS:
@@ -128,7 +140,7 @@ def visit_module(self, node):
128140
# restore sys.path
129141
sys.path = sys_path
130142

131-
FixtureChecker.pytest_fixtures = fixture_collector.fixtures
143+
FixtureChecker._pytest_fixtures = fixture_collector.fixtures
132144

133145
legitimate_failure_paths = set(
134146
collection_report.nodeid
@@ -212,11 +224,92 @@ def visit_functiondef(self, node):
212224
if _is_pytest_mark_usefixtures(decorator):
213225
# save all visited fixtures
214226
for arg in decorator.args:
215-
self.invoked_with_usefixtures.add(arg.value)
227+
self._invoked_with_usefixtures.add(arg.value)
216228
if int(pytest.__version__.split(".")[0]) >= 3 and _is_pytest_fixture(
217229
decorator, fixture=False
218230
):
219231
# raise deprecated warning for @pytest.yield_fixture
220232
self.add_message("deprecated-pytest-yield-fixture", node=node)
221233
for arg in node.args.args:
222-
self.invoked_with_func_args.add(arg.name)
234+
self._invoked_with_func_args.add(arg.name)
235+
236+
# pylint: disable=bad-staticmethod-argument # The function itself is an if-return logic.
237+
@staticmethod
238+
def patch_add_message(
239+
self, msgid, line=None, node=None, args=None, confidence=None, col_offset=None
240+
):
241+
"""
242+
- intercept and discard unwanted warning messages
243+
"""
244+
# check W0611 unused-import
245+
if msgid == "unused-import":
246+
# actual attribute name is not passed as arg so...dirty hack
247+
# message is usually in the form of '%s imported from %s (as %)'
248+
message_tokens = args.split()
249+
fixture_name = message_tokens[0]
250+
251+
# ignoring 'import %s' message
252+
if message_tokens[0] == "import" and len(message_tokens) == 2:
253+
pass
254+
255+
# fixture is defined in other modules and being imported to
256+
# conftest for pytest magic
257+
elif (
258+
isinstance(node.parent, astroid.Module)
259+
and node.parent.name.split(".")[-1] == "conftest"
260+
and fixture_name in FixtureChecker._pytest_fixtures
261+
):
262+
return
263+
264+
# imported fixture is referenced in test/fixture func
265+
elif (
266+
fixture_name in FixtureChecker._invoked_with_func_args
267+
and fixture_name in FixtureChecker._pytest_fixtures
268+
):
269+
if _is_same_module(
270+
fixtures=FixtureChecker._pytest_fixtures,
271+
import_node=node,
272+
fixture_name=fixture_name,
273+
):
274+
return
275+
276+
# fixture is referenced in @pytest.mark.usefixtures
277+
elif (
278+
fixture_name in FixtureChecker._invoked_with_usefixtures
279+
and fixture_name in FixtureChecker._pytest_fixtures
280+
):
281+
if _is_same_module(
282+
fixtures=FixtureChecker._pytest_fixtures,
283+
import_node=node,
284+
fixture_name=fixture_name,
285+
):
286+
return
287+
288+
# check W0613 unused-argument
289+
if (
290+
msgid == "unused-argument"
291+
and _can_use_fixture(node.parent.parent)
292+
and isinstance(node.parent, astroid.Arguments)
293+
):
294+
if node.name in FixtureChecker._pytest_fixtures:
295+
# argument is used as a fixture
296+
return
297+
298+
fixnames = (
299+
arg.name for arg in node.parent.args if arg.name in FixtureChecker._pytest_fixtures
300+
)
301+
for fixname in fixnames:
302+
if node.name in FixtureChecker._pytest_fixtures[fixname][0].argnames:
303+
# argument is used by a fixture
304+
return
305+
306+
# check W0621 redefined-outer-name
307+
if (
308+
msgid == "redefined-outer-name"
309+
and _can_use_fixture(node.parent.parent)
310+
and isinstance(node.parent, astroid.Arguments)
311+
and node.name in FixtureChecker._pytest_fixtures
312+
):
313+
return
314+
315+
FixtureChecker._original_add_message(self, msgid, line, node, args, confidence, col_offset)

pylint_pytest/checkers/variables.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

tests/test_redefined_outer_name.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import pytest
22
from base_tester import BasePytestTester
3+
from pylint.checkers.variables import VariablesChecker
34

45
from pylint_pytest.checkers.fixture import FixtureChecker
5-
from pylint_pytest.checkers.variables import CustomVariablesChecker
66

77

88
class TestRedefinedOuterName(BasePytestTester):
99
CHECKER_CLASS = FixtureChecker
10-
IMPACTED_CHECKER_CLASSES = [CustomVariablesChecker]
10+
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1111
MSG_ID = "redefined-outer-name"
1212

1313
@pytest.mark.parametrize("enable_plugin", [True, False])

tests/test_regression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import pytest
22
from base_tester import BasePytestTester
3+
from pylint.checkers.variables import VariablesChecker
34

45
from pylint_pytest.checkers.fixture import FixtureChecker
5-
from pylint_pytest.checkers.variables import CustomVariablesChecker
66

77

88
class TestRegression(BasePytestTester):
99
"""Covering some behaviors that shouldn't get impacted by the plugin"""
1010

1111
CHECKER_CLASS = FixtureChecker
12-
IMPACTED_CHECKER_CLASSES = [CustomVariablesChecker]
12+
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1313
MSG_ID = "regression"
1414

1515
@pytest.mark.parametrize("enable_plugin", [True, False])

tests/test_unused_argument.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import pytest
22
from base_tester import BasePytestTester
3+
from pylint.checkers.variables import VariablesChecker
34

45
from pylint_pytest.checkers.fixture import FixtureChecker
5-
from pylint_pytest.checkers.variables import CustomVariablesChecker
66

77

88
class TestUnusedArgument(BasePytestTester):
99
CHECKER_CLASS = FixtureChecker
10-
IMPACTED_CHECKER_CLASSES = [CustomVariablesChecker]
10+
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1111
MSG_ID = "unused-argument"
1212

1313
@pytest.mark.parametrize("enable_plugin", [True, False])

tests/test_unused_import.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import pytest
22
from base_tester import BasePytestTester
3+
from pylint.checkers.variables import VariablesChecker
34

45
from pylint_pytest.checkers.fixture import FixtureChecker
5-
from pylint_pytest.checkers.variables import CustomVariablesChecker
66

77

88
class TestUnusedImport(BasePytestTester):
99
CHECKER_CLASS = FixtureChecker
10-
IMPACTED_CHECKER_CLASSES = [CustomVariablesChecker]
10+
IMPACTED_CHECKER_CLASSES = [VariablesChecker]
1111
MSG_ID = "unused-import"
1212

1313
@pytest.mark.parametrize("enable_plugin", [True, False])

0 commit comments

Comments
 (0)