pytest: Slowdown of pytest collection in sqlalchemy in python 3.9+

  • a detailed description of the bug or problem you are having
  • output of pip list from the virtual environment you are using
  • pytest and operating system versions
  • minimal example if possible

We noticed that from python 3.9 pytest takes a significantly more time (~4x) to collect the tests for sqlalchemy. You can run the following docker to compare. python 3.7 and 3.8 are ok (not fast not slow), python 3.9 and 3.10 are noticeably slower

docker run -ti --name py --rm python:3.7 bash -c 'git clone https://github.com/sqlalchemy/sqlalchemy.git --depth 100; pip install pytest; cd sqlalchemy; time pytest --collect-only'
docker run -ti --name py --rm python:3.8 bash -c 'git clone https://github.com/sqlalchemy/sqlalchemy.git --depth 100; pip install pytest; cd sqlalchemy; time pytest --collect-only'
docker run -ti --name py --rm python:3.9 bash -c 'git clone https://github.com/sqlalchemy/sqlalchemy.git --depth 100; pip install pytest; cd sqlalchemy; time pytest --collect-only'
docker run -ti --name py --rm python:3.10 bash -c 'git clone https://github.com/sqlalchemy/sqlalchemy.git --depth 100; pip install pytest; cd sqlalchemy; time pytest --collect-only'

the times on my pc are as floows:

#py37
real    0m27.949s
user    0m26.717s
sys     0m1.220s
#py38
real    0m27.952s
user    0m26.893s
sys     0m1.050s
# py39
real    1m45.581s
user    1m42.574s
sys     0m2.990s
# py310
real    1m46.255s
user    1m43.648s
sys     0m2.590s

cc @zzzeek

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 31 (14 by maintainers)

Most upvoted comments

Ok, I had to use hookwrapper in pytest_runtest_teardown since that finalizer needs to run after all the class fixture ones, but it seems to be working,

The last version is on gerrit https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/3332

edit, yes the test all pass.

Having the previous behaviour of addfinalizer would be nice to have, but I think this can be closed. Thanks a lot for all the support and help!

alternatively is there an approximate date you are aiming towards for v7?

We are planning a release candidate pretty soon, likely within the week, so shouldn’t be far.

We also tripping an assert while adding a finalizer

The traceback shows get_test_class_from_fn, does that function return pytest.Class both in v6 and v7? If so things should just work, you should be able to attach finalizers to classes.

are are trying to add it in pytest_runtest_setup here

I don’t see get_test_class_from_fn in the link, was that removed afterwards?

Ahh I see it in Gerrit. You actually don’t need that function, item.getparent(pytest.Class) will return the class for that item, or None in case it doesn’t have a class (it is a free function). And this will work in v6 and v7.

Same is valid for other places like this:

    items[:] = sorted(
        newitems,
        key=lambda item: (
            get_test_class_from_fn(item).parent.name,
            get_test_class_from_fn(item).name,
            item.name,
        ),
    )

Which can be written as:

    items[:] = sorted(
        newitems,
        key=lambda item: (
            item.getparent(pytest.Module).name,
            item.getparent(pytest.Class).name,
            item.name,
        ),
    )

item.getparent(pytest.Module) will always return a pytest.Module of course.

cant wait for the speedup! ive been pretty depressed about the slow collection times for some time now, there have been different “slow collection” issues over the years (like in the 4.x series too with fixtures).

btw I’ve started supporting pytest v7 here https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/3332

edit: apart from the addfinalizer issue above, everything seems to be working with v7. I confirm that also in windows the speedup is very significant, 1:33s down to 18s

edit of edit: no still some work to be done. we seem to be loosing the class variables from the test function that handle the skips. probably related to the parent change

Thanks for all the help and suggestions!

Back to the original issue, @CaselIT you can try the commit before #9277 was merged, i.e.

pip install git+https://github.com/pytest-dev/pytest.git@17b38259fde89caaf44a4e578d9f62d001a894c9

I confirm that this commit does collect and also the slowdown in gone:

py 3.10 with 17b38259fde89caaf44a4e578d9f62d001a894c9
real    0m25.652s
user    0m24.515s
sys     0m1.131s

would it be possible to backport that improvement to the v6 series? @bluetech alternatively is there an approximate date you are aiming towards for v7?


Regarding your suggestion @nicoddemus that fixes the collection but it will require other changes. The issue is that we use parent.parent in many places and we now should just use parent. This change seems one that may trip many places even if they are not using Instance directly.

We also tripping an assert while adding a finalizer:

  File "test\..\lib\sqlalchemy\testing\plugin\pytestplugin.py", line 422, in pytest_runtest_setup
    get_test_class_from_fn(item).addfinalizer(finalize)
  File "lib\site-packages\_pytest\nodes.py", line 405, in addfinalizer
    self.session._setupstate.addfinalizer(fin, self)
  File "\lib\site-packages\_pytest\runner.py", line 510, in addfinalizer
    assert node in self.stack, (node, self.stack)
AssertionError: (<Class TestAsyncAdaptedQueue>, {})

pdbing there in v6 shows that the_class.session._setupstate.statck is also empty (and stack is a list not a dict):

# pdb on v6.2.5
-> get_test_class_from_fn(item).addfinalizer(finalize)
(Pdb) get_test_class_from_fn(item).session._setupstate.stack
[]

are are trying to add it in pytest_runtest_setup here: https://github.com/sqlalchemy/sqlalchemy/blob/9c5dfddfbd405a72d33b72ed097690a6c2c88b3a/lib/sqlalchemy/testing/plugin/pytestplugin.py#L400

what’s the suggested way of adding a class finalizer?