Skip to content

TypeError: A Message class can only inherit from Message when using pytester and a per-test import google.api_core.exceptions #9981

Open
@AshleyT3

Description

@AshleyT3

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:

  1. Create Python venv for test.
  2. pip install pytest
  3. pip install google-api-core
  4. In test directory, create conftest.py with pytest_plugins = "pytester"
  5. In test directory, create test_example.py to hold the tests leading to repro of issue.
  6. 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
  1. 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
  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    plugin: pytesterrelated to the pytester builtin plugintype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions