Skip to content

Commit 9cf2001

Browse files
committed
Enhance fixture decorator detection and add assignment validation tests
1 parent 298e88f commit 9cf2001

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

src/community_of_python_flake8_plugin/checks/function_verb.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ def check_is_fixture_decorator(decorator: ast.expr) -> bool:
4747
return decorator.id == "fixture"
4848
if isinstance(decorator, ast.Attribute):
4949
return decorator.attr == "fixture" and isinstance(decorator.value, ast.Name) and decorator.value.id == "pytest"
50+
# Handle cases where decorator might be a call like @pytest.fixture(name="events")
51+
if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute):
52+
return (
53+
decorator.func.attr == "fixture"
54+
and isinstance(decorator.func.value, ast.Name)
55+
and decorator.func.value.id == "pytest"
56+
)
57+
if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Name):
58+
return decorator.func.id == "fixture"
5059
return False
5160

5261

tests/test_plugin.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@ def test_type_annotation_validations(input_source: str, expected_output: list[st
156156
"from pytest import fixture\n@fixture\ndef some_fixture(arg: fixture): pass",
157157
[],
158158
),
159+
# No violation: pytest fixture with name parameter should be exempt from COP009
160+
(
161+
"import pytest\n@pytest.fixture(name='events')\ndef fixture_events() -> list[dict]:\n return []",
162+
[],
163+
),
164+
# No violation: pytest fixture with name parameter should be exempt from COP009
165+
(
166+
"import pytest\n@pytest.fixture(name='events')\ndef fixture_events() -> list[dict]:\n return []",
167+
[],
168+
),
159169
# COP009: faker.Faker annotation doesn't exempt function naming rules
160170
(
161171
"import faker\ndef some_func(arg: faker.Faker): pass",
@@ -361,6 +371,25 @@ def test_dataclass_validations(input_source: str, expected_output: list[str]) ->
361371
) == sorted(expected_output)
362372

363373

374+
@pytest.mark.parametrize(
375+
("input_source", "expected_output"),
376+
[
377+
# COP005: Module-level annotated assignment should be treated as variable, not attribute
378+
("v: int = 1", ["COP005", "COP003"]),
379+
# COP005: Module-level Final assignment should be treated as variable, not attribute
380+
("c: typing.Final = CreditStatementClient()", ["COP005"]),
381+
# COP004: Class-level annotated assignment should still be treated as attribute
382+
("class ExampleClass:\n a: int = 1", ["COP012", "COP004"]),
383+
# COP004: Class-level Final assignment should still be treated as attribute
384+
("class ExampleClass:\n client: typing.Final = CreditStatementClient()", ["COP012", "COP004"]),
385+
],
386+
)
387+
def test_module_vs_class_level_assignments(input_source: str, expected_output: list[str]) -> None:
388+
assert sorted(
389+
[item[2].split(" ")[0] for item in CommunityOfPythonFlake8Plugin(ast.parse(input_source)).run()] # noqa: COP011
390+
) == sorted(expected_output)
391+
392+
364393
@pytest.mark.parametrize(
365394
("input_source", "expected_output"),
366395
[

0 commit comments

Comments
 (0)