Skip to content

Comments

Improve error message for module-level __getattr__ issues#14170

Open
veeceey wants to merge 3 commits intopytest-dev:mainfrom
veeceey:fix/issue-8265-getattr-error-message
Open

Improve error message for module-level __getattr__ issues#14170
veeceey wants to merge 3 commits intopytest-dev:mainfrom
veeceey:fix/issue-8265-getattr-error-message

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 8, 2026

Summary

Fixes #8265

This PR improves the error message when a module-level __getattr__ returns None instead of raising AttributeError.

Problem

When a module defines a custom __getattr__ that returns None instead of raising AttributeError, pytest gives a cryptic error message:

TypeError: got None instead of Mark

This is confusing for users who don't understand what a "Mark" is or why pytest is looking for one.

Solution

Added a special check in get_unpacked_marks() to detect when the pytestmark attribute resolves to None and emit a PytestCollectionWarning that explains:

  1. What the likely cause is (module-level __getattr__)
  2. What the fix is (raise AttributeError)

Changes

  • Modified get_unpacked_marks() in /src/_pytest/mark/structures.py to detect None and emit a warning
  • Added test case to verify the new warning message
  • Added changelog entry

Test plan

When a module defines a custom __getattr__ that returns None instead
of raising AttributeError, pytest now provides a helpful error message
explaining the likely cause and suggesting the fix.

The previous error was: "got None instead of Mark"
The new error includes: "this is likely caused by a module-level
__getattr__ that does not raise AttributeError for missing attributes"

Fixes pytest-dev#8265
@psf-chronographer psf-chronographer bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Feb 8, 2026
Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong place for the check there should be a warning triggered in the helper to get that list

…marks

Per reviewer feedback, move the check for module-level __getattr__
returning None to get_unpacked_marks() (the helper that fetches the
mark list) instead of normalize_mark_list(). Also change from raising
a TypeError to emitting a PytestCollectionWarning, so the module can
still be collected normally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@veeceey
Copy link
Author

veeceey commented Feb 9, 2026

Thanks for the review @RonnyPfannschmidt! You're right — the check was in the wrong place.

I've moved it from normalize_mark_list() to get_unpacked_marks(), which is the helper that actually fetches the pytestmark attribute list. Now when a module-level __getattr__ returns None instead of raising AttributeError, a PytestCollectionWarning is emitted (instead of raising a TypeError), and the module continues to be collected normally.

Changes in the latest push:

  • Moved the None check to get_unpacked_marks() where getattr(obj, "pytestmark", []) is called
  • Changed from raising TypeError to emitting a PytestCollectionWarning
  • Updated the test and changelog accordingly

@veeceey
Copy link
Author

veeceey commented Feb 19, 2026

Hi @RonnyPfannschmidt -- just following up on this. I've addressed your feedback by moving the check from normalize_mark_list() to get_unpacked_marks() and switching from raising a TypeError to emitting a PytestCollectionWarning, as you suggested. The test and changelog have been updated accordingly.

Would you be able to take another look when you get a chance? Happy to make further adjustments if anything else needs tweaking. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cryptic Error Message when module-level __getattr__ fails to raise AttributeError

2 participants