Skip to content

[prerelease] AttributeError: module 'homeassistant.setup' has no attribute '__code__' #9391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
The-Compiler opened this issue Dec 7, 2021 · 8 comments · Fixed by #9441
Closed
Labels
plugin: nose related to the nose integration builtin plugin type: regression indicates a problem that was introduced in a release which was working previously
Milestone

Comments

@The-Compiler
Copy link
Member

The-Compiler commented Dec 7, 2021

Via Twitter, @cgtobi reports:

Not sure if this is a regression, but the same test works with 6.2.5.

And indeed I can reproduce the failure via tox -e py39 -- -x tests/test_setup.py in the home-assistant/core repository (after sneaking in the pytest main branch via pip once the initial run passed - note that it will need tox-pip-version and setting things up will take quite a while):


――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ERROR at setup of TestSetup.test_validate_component_config ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
[gw0] linux -- Python 3.9.9 /home/florian/tmp/core/.tox/py39/bin/python

cls = <class '_pytest.runner.CallInfo'>, func = <function call_runtest_hook.<locals>.<lambda> at 0x7f01d2b9d4c0>, when = 'setup', reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: "Callable[[], TResult]",
        when: "Literal['collect', 'setup', 'call', 'teardown']",
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

../../proj/pytest/src/_pytest/runner.py:340: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../proj/pytest/src/_pytest/runner.py:261: in <lambda>
    lambda: ihook(item=item, **kwds), when=when, reraise=reraise
.tox/py39/lib/python3.9/site-packages/pluggy/_hooks.py:265: in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
.tox/py39/lib/python3.9/site-packages/pluggy/_manager.py:80: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../../proj/pytest/src/_pytest/runner.py:156: in pytest_runtest_setup
    item.session._setupstate.setup(item)
../../proj/pytest/src/_pytest/runner.py:501: in setup
    raise exc
../../proj/pytest/src/_pytest/runner.py:498: in setup
    col.setup()
../../proj/pytest/src/_pytest/python.py:1694: in setup
    self._request._fillfixtures()
../../proj/pytest/src/_pytest/fixtures.py:578: in _fillfixtures
    item.funcargs[argname] = self.getfixturevalue(argname)
../../proj/pytest/src/_pytest/fixtures.py:591: in getfixturevalue
    fixturedef = self._get_active_fixturedef(argname)
../../proj/pytest/src/_pytest/fixtures.py:610: in _get_active_fixturedef
    self._compute_fixture_value(fixturedef)
../../proj/pytest/src/_pytest/fixtures.py:693: in _compute_fixture_value
    fixturedef.execute(request=subrequest)
../../proj/pytest/src/_pytest/fixtures.py:1068: in execute
    result = hook.pytest_fixture_setup(fixturedef=self, request=request)
.tox/py39/lib/python3.9/site-packages/pluggy/_hooks.py:265: in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
.tox/py39/lib/python3.9/site-packages/pluggy/_manager.py:80: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
../../proj/pytest/src/_pytest/fixtures.py:1122: in pytest_fixture_setup
    result = call_fixture_func(fixturefunc, request, kwargs)
../../proj/pytest/src/_pytest/fixtures.py:917: in call_fixture_func
    fixture_result = next(generator)
../../proj/pytest/src/_pytest/python.py:540: in xunit_setup_module_fixture
    _call_with_optional_argument(setup_module, request.module)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

func = <module 'homeassistant.setup' from '/home/florian/tmp/core/homeassistant/setup.py'>, arg = <module 'tests.test_setup' from '/home/florian/tmp/core/tests/test_setup.py'>

    def _call_with_optional_argument(func, arg) -> None:
        """Call the given function with the given argument if func accepts one argument, otherwise
        calls func without arguments."""
>       arg_count = func.__code__.co_argcount
E       AttributeError: module 'homeassistant.setup' has no attribute '__code__'

../../proj/pytest/src/_pytest/python.py:749: AttributeError

Relevant code here: https://github.com/home-assistant/core/blob/dev/tests/test_setup.py

I was able to bisect this to 89f0b5b, which is:

nose: fix class- and module-level fixture behavior by bluetech · Pull Request #9273 · pytest-dev/pytest

I suspect the from homeassistant import config_entries, setup results in a setup name inside the test module, which pytest now picks up as a nose-style setup function but did not before or something?

cc @bluetech

@The-Compiler The-Compiler added type: regression indicates a problem that was introduced in a release which was working previously plugin: nose related to the nose integration builtin plugin labels Dec 7, 2021
@The-Compiler The-Compiler added this to the 7.0 milestone Dec 7, 2021
@bluetech
Copy link
Member

bluetech commented Dec 7, 2021

Thanks for bisecting.

That commit is a bugfix I'd say. I've noticed it while considering deprecating the nose compat however I decided against it for now as it would be useless churn for users.

homeassistent should either avoid these names, or probably better, disable the nose plugin (-p no:nose). That will work on all pytest versions, and avoid other nose surprises.

@The-Compiler
Copy link
Member Author

Fair, but I wonder if we could/should show a better message for that case?

@cgtobi
Copy link

cgtobi commented Dec 7, 2021

Wow, thanks for the effort.

@bluetech
Copy link
Member

bluetech commented Dec 7, 2021

Fair, but I wonder if we could/should show a better message for that case?

Better error would be good (also relevant for pytest own names e.g. setup_module and such). IIUC setup in this case is a module which causes the error. I can look into improving the error message - can't guarantee when though :) Shall we open a separate issue or use this one to track this?

@bluetech
Copy link
Member

bluetech commented Dec 7, 2021

@cgtobi thanks for testing the rc 😀

@The-Compiler
Copy link
Member Author

I just took a quick look at this with @cgtobi - Home Assistant actually uses nose-style setup/teardown in a couple of places, so -p no:nose leads to some issues.

I was wondering why this worked just fine for them despite the setup import in earlier pytest versions.

I think the reason is that before, we used call_optional for module-level setup, which silently ignores non-callable objects.

However, with #9273, we now call it unconditionally despite it being a module object, which then results in the AttributeError for __code__.

Despite surely being a bit unorthodox in moduleassistant's code, I think this is a regression. Not sure what the best behavior here would be:

  1. Either keep it as before and silently ignore it when it's not callable, or
  2. Turn non-callable setup/teardown into an error (though we might want to deprecate it first, dunno?)

@nicoddemus
Copy link
Member

nicoddemus commented Dec 23, 2021

Either keep it as before and silently ignore it when it's not callable, or

I vote for this, pytest should be "smart" here and figure out that an object is not callable. Not sure 2) would help users, other than forcing them to rename a module (from foo import setup as something_else).

@nicoddemus
Copy link
Member

Btw thanks for the report @cgtobi and for the investigation @The-Compiler!

bluetech added a commit to bluetech/pytest that referenced this issue Dec 25, 2021
Since commit 89f0b5b cases as in the
added test started to fail, like they do for the standard pytest names
(`setup_module` etc). But the name `setup` in particular is way too
common for us to start taking it over more aggressively, so restore the
previous behavior which required the object to be callable.

Fix pytest-dev#9391.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: nose related to the nose integration builtin plugin type: regression indicates a problem that was introduced in a release which was working previously
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants