Description
Python: 3.10.2 and 3.9.12
pytest: 7.1.2
google-api-core: 2.8.0
Brief description: There seems to be some compatibility issue when using both pytester and google.api_core.exceptions in a case where google.api_core.exceptions is loaded after initially module import such as when loading by a factory creating an instance of an object requiring it.
Detailed Description:
If you have a test using a factory that itself imports a module importing google.api_core.exceptions, subsequent tests using the same factory will fail without applying one of the two workarounds mentioned in details below. Offhand, there seems to be state maintained by _api_implementation.cp310-win_amd64.pyd about which Message base is allowed which becomes mismatched to the actual current Message base class utilized in calls to instantiate... and this seems to only happen when using pytester (not that pytester is at fault).
I do find some other search results out there for this exception... it seems rare and affects certain products using python and related imports that occur during their use, pytest being another. I do not have a native env setup or debug build of google.api_core.exceptions, pytester, etc., so at this point in time, I am not yet sure if it's an issue with google.api_core.exceptions or pytest. I could dig further to see the nature of the interactions but wanted to check here first, see if any pytest/pytester experts may already know something on this.
Steps:
- Create Python venv for test.
pip install pytest
pip install google-api-core
- In test directory, create
conftest.py
withpytest_plugins = "pytester"
- In test directory, create
test_example.py
to hold the tests leading to repro of issue. - Create an initial/first test as follows (this simplification sufficiently causes the same effect as a factory situation):
def test_example(
pytester: Pytester, # Comment this out as a second workaround.
):
import google.api_core.exceptions
- Create a second test as follows:
def test_example2():
import google.api_core.exceptions # Fails with: TypeError: A Message class can only inherit from Message
- Run all the tests at once, in typical sequential order of test_example, test_example2. Observe test_example2 fails upon import of google.api_core.exceptions.
Known workarounds:
- Do not use pytester in the first test (before the first load of google.api_core.exceptions).
- Directly import google.api_core.exceptions at the top of test_example.py.
Additional information:
protobuf/pyext/message.cc has the following check believed to be throwing the exception. I suspect PythonMessage_class has changed given the other checks seem to match what is passed (see details/stacks further below).
// Check bases: only (), or (message.Message,) are allowed
if (!(PyTuple_GET_SIZE(bases) == 0 ||
(PyTuple_GET_SIZE(bases) == 1 &&
PyTuple_GET_ITEM(bases, 0) == PythonMessage_class))) {
PyErr_SetString(PyExc_TypeError,
"A Message class can only inherit from Message");
return nullptr;
}
In the case where the issue occurs, the following two stacks occur, one for each test causing the import...
First, during 'test_example' test:
BuildMessage (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\internal\builder.py:85) BuildTopDescriptorsAndMessages (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\internal\builder.py:108) (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\duration_pb2.py:19) (.\side-tests-venv-3.10.2\Lib\site-packages\google\rpc\error_details_pb2.py:31) (.\side-tests-venv-3.10.2\Lib\site-packages\google\api_core\exceptions.py:29) test_example (.\side_tests\test_example.py:31) pytest_pyfunc_call (.\side-tests-venv-3.10.2\Lib\site-packages\_pytest\python.py:192) ... Message ID check in debugger: id(_message.Message) 1995227571856
Second, during 'test_example2' test:
BuildMessage (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\internal\builder.py:85) BuildTopDescriptorsAndMessages (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\internal\builder.py:108) (.\side-tests-venv-3.10.2\Lib\site-packages\google\protobuf\duration_pb2.py:19) (.\side-tests-venv-3.10.2\Lib\site-packages\google\rpc\error_details_pb2.py:31) (.\side-tests-venv-3.10.2\Lib\site-packages\google\api_core\exceptions.py:29) test_example2 (.\side_tests\test_example.py:34) pytest_pyfunc_call (.\side-tests-venv-3.10.2\Lib\site-packages\_pytest\python.py:192) ... Message ID check in debugger: id(_message.Message) 1995227645776
pytest --version
pytest 7.1.2
pip list
Package Version
------------------------ -----------
astroid 2.11.5
atomicwrites 1.4.0
attrs 21.4.0
cachetools 5.1.0
certifi 2022.5.18.1
charset-normalizer 2.0.12
colorama 0.4.4
dill 0.3.5
google-api-core 2.8.0
google-auth 2.6.6
googleapis-common-protos 1.56.1
idna 3.3
iniconfig 1.1.1
isort 5.10.1
lazy-object-proxy 1.7.1
mccabe 0.7.0
packaging 21.3
pip 22.1
platformdirs 2.5.2
pluggy 1.0.0
protobuf 3.20.1
py 1.11.0
pyasn1 0.4.8
pyasn1-modules 0.2.8
pylint 2.13.9
pyparsing 3.0.9
pytest 7.1.2
requests 2.27.1
rsa 4.8
setuptools 58.1.0
six 1.16.0
tomli 2.0.1
urllib3 1.26.9
wrapt 1.14.1
conftest.py:
pytest_plugins = "pytester"
test_example.py:
# Setup:
# pip install pylint
# pip install google-api-core
# When this is run, the following exception occurs after
# the first import google.api_core.exceptions
# TypeError: A Message class can only inherit from Message
# conftest.py should contain the following:
# pytest_plugins = "pytester"
# One workarund is to uncomment the following (import exceptions directly).
# import google.api_core.exceptions
# Another workaround is to comment out pytester as an argument to the
# test *before* the first import google.api_core.exceptions.
# The issue seems to be some mixture of a test using pytester while
# it imports google.api_core.exceptions for the first time.
# pylint: disable=unused-import,wrong-import-position
from pytest import (
Pytester,
)
def test_example(
pytester: Pytester, # Comment this out as a second workaround.
):
# The real scenario had the test calling a factory function
# in a module that itself imported google.api_core.exceptions.
# Having each test import as follows is a simplfication of the original issue.
import google.api_core.exceptions
def test_example2():
import google.api_core.exceptions # Fails with: TypeError: A Message class can only inherit from Message