Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/8265.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Emit a ``PytestCollectionWarning`` when a module-level ``__getattr__`` returns ``None`` for ``pytestmark`` instead of raising ``AttributeError``.

Previously this caused a cryptic ``TypeError: got None instead of Mark`` error.
Now pytest issues a helpful warning and continues collecting the module normally.
14 changes: 13 additions & 1 deletion src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from _pytest.outcomes import fail
from _pytest.raises import AbstractRaises
from _pytest.scope import _ScopeName
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnknownMarkWarning


Expand Down Expand Up @@ -443,7 +444,18 @@ def get_unpacked_marks(
mark_list.append(item)
else:
mark_attribute = getattr(obj, "pytestmark", [])
if isinstance(mark_attribute, list):
if mark_attribute is None:
warnings.warn(
"Module defines a `__getattr__` which returns None for "
"'pytestmark' instead of raising AttributeError. "
"Make sure `__getattr__` raises AttributeError for "
"attributes it does not provide. "
"See https://github.com/pytest-dev/pytest/issues/8265",
PytestCollectionWarning,
stacklevel=2,
)
mark_list = []
elif isinstance(mark_attribute, list):
mark_list = mark_attribute
else:
mark_list = [mark_attribute]
Expand Down
28 changes: 28 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -1344,3 +1344,31 @@ def test_fixture_disallowed_between_marks() -> None:
@pytest.mark.usefixtures("tmp_path")
def foo():
raise NotImplementedError()


def test_module_getattr_without_attributeerror(pytester: Pytester) -> None:
"""
Test that a helpful warning is emitted when a module-level
__getattr__ returns None instead of raising AttributeError.

Regression test for https://github.com/pytest-dev/pytest/issues/8265
"""
pytester.makepyfile(
"""
def __getattr__(key):
# Bug: should raise AttributeError, but returns None
return None

def test_something():
assert True
"""
)
result = pytester.runpytest("-W", "always::pytest.PytestCollectionWarning")
result.stdout.fnmatch_lines(
[
"*PytestCollectionWarning*__getattr__*returns None*AttributeError*",
]
)
# The module is buggy (__getattr__ returns None for all attributes),
# so no tests are collected, but pytest should NOT crash with a TypeError.
assert result.ret != ExitCode.INTERNAL_ERROR