-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
I just ran into an error that was very difficult to figure out. This is not a bug in pytest perse, but it is an area that might benefit from having a better error message.
In Python 3.7+ you are now allowed to define a custom __getattr__ at the module level. I find this incredibly useful giving users deprecation errors when they try to use something that was recently moved or removed. However, I made a mistake in my last one, and instead of raising an AttributeError in the fallback case, I forgot and let the default None return.
This actually doesn't hurt anything in normal usage, because only attributes that exist are ever accessed. However, it seems like pytest attempts to do a getattr at the module level in normalize_mark_list defined in _pytest.mark.structures. Thus if I define the file:
#pytest_getattr_mwe.py
def __getattr__(key):
return None
# raise AttributeError(key)
def test_1():
return 1and run pytest pytest_getattr_mwe.py I get:
(py38) joncrall@Ooo:~/misc/tests/python$ pytest pytest_getattr_mwe.py
=============================================================================== test session starts ================================================================================
platform linux -- Python 3.8.2, pytest-6.2.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/joncrall/misc/tests/python
plugins: celery-4.4.7, timeout-1.4.1, notebook-0.6.0, hydra-core-1.0.5, lazy-fixture-0.6.3, cov-2.8.1, xdoctest-0.15.2
collected 0 items / 1 error
====================================================================================== ERRORS ======================================================================================
______________________________________________________________________ ERROR collecting pytest_getattr_mwe.py ______________________________________________________________________
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/runner.py:311: in from_call
result: Optional[TResult] = func()
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/runner.py:341: in <lambda>
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/python.py:503: in collect
self._inject_setup_module_fixture()
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/python.py:516: in _inject_setup_module_fixture
self.obj, ("setUpModule", "setup_module")
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/python.py:295: in obj
self.own_markers.extend(get_unpacked_marks(self.obj))
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/mark/structures.py:353: in get_unpacked_marks
return normalize_mark_list(mark_list)
../../../.local/conda/envs/py38/lib/python3.8/site-packages/_pytest/mark/structures.py:367: in normalize_mark_list
raise TypeError(f"got {mark!r} instead of Mark")
E TypeError: got None instead of Mark
Perhaps it would be possible to improve this error message? For instance if mark is None maybe hint that __getattr__ may have been ill-defined? (I'm not sure if this can happen in other cases).
The thing that made this so hard for me is that I have no idea what a pytest mark does. I try not to use any of its magic or decorator features in favor of more explicit code. Thus when I got an error involving whatever a mark, I was quite confused.
At the very least, I hope posting this will help some poor soul who googles the error message "TypeError: got None instead of Mark", and instead of getting nothing like I did, maybe this will come up and then they will realize they forgot to raise AttributeError at the end of their __getattr__.