Skip to content

Conversation

oliver-sanders
Copy link
Member

Getting this change out the way now before they drop support for the status quo.

To test, find a differently scoped fixture and jam a breakpoint into the code to ensure that it is called the expected number of times.

E.g, add a breakpoint into tests/integration/network/test_client.py::harness, then run pytest -n0 --dist=no tests/integration/network/test_client.py::test_graphql tests/integration/network/test_client.py::test_protobuf. The breakpoint will only be hit once.

Check List

  • I have read CONTRIBUTING.md and added my name as a Code Contributor.
  • Contains logically grouped changes (else tidy your branch by rebase).
  • Does not contain off-topic changes (use other PRs for other changes).
  • Applied any dependency changes to both setup.cfg (and conda-environment.yml if present).
  • Tests are included (or explain why tests are not needed).
  • Changelog entry included if this is a change that can affect users
  • Cylc-Doc pull request opened if required at cylc/cylc-doc/pull/XXXX.
  • If this is a bug fix, PR should be raised against the relevant ?.?.x branch.

@oliver-sanders oliver-sanders added this to the 8.5.0 milestone Apr 14, 2025
@oliver-sanders oliver-sanders self-assigned this Apr 14, 2025
@oliver-sanders oliver-sanders force-pushed the 5995 branch 2 times, most recently from 747c583 to 8cd327c Compare April 14, 2025 10:59
… code

* Closes https://github.com/cylc/cylc-flow/issues/5995t
* Asynchronous fixtures should use `pytest_asyncio.fixture` rather than
  `pytest.fixture` when attempting to override the default event loop
  scope.
* In practice, this has to be done whenever the fixture scope is not
  "function" (which is the pytest_asyncio default).
@oliver-sanders oliver-sanders marked this pull request as ready for review April 14, 2025 15:21
Comment on lines +121 to 125
# BACK COMAPAT: pytest-asyncio 0.21.2
# FROM: python 3
# TO: python 3.7
# URL: https://github.com/cylc/cylc-flow/pull/6726
pytest-asyncio>=0.21.2,!=0.23.*
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't drop 0.21 support just yet, because it's the last version to support Python 3.7 :(

Comment on lines +268 to +291
if pytest_asyncio.__version__.startswith('0.21'):
# BACK COMPAT: event_loop
# FROM: python 3
# TO: python 3.7
# URL: https://github.com/cylc/cylc-flow/pull/6726
@pytest_asyncio.fixture(scope='module')
def event_loop():
"""This fixture defines the event loop used for each test.
The default scoping for this fixture is "function" which means that all
async fixtures must have "function" scoping.
Defining `event_loop` as a module scoped fixture opens the door to
module scoped fixtures but means all tests in a module will run in the
same event loop. This is fine, it's actually an efficiency win but also
something to be aware of.
See: https://github.com/pytest-dev/pytest-asyncio/issues/171
"""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
# gracefully exit async generators
loop.run_until_complete(loop.shutdown_asyncgens())
# cancel any tasks still running in this event loop
for task in asyncio.all_tasks(loop):
task.cancel()
loop.close()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get rid of this soon.

@oliver-sanders
Copy link
Member Author

Note: This does not silence the Python warnings because setting the default loop scope (as recommended) appears to break our ability to override it. Other projects seem to be suppressing these warnings by the look of their GH issues. Soon they will finish this migration and the warnings will vanish.

@MetRonnie
Copy link
Member

According to the warnings:

The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future.

So for now, @pytest_asyncio.fixture(scope="module") is the same as scope="module", loop_scope="module", but in the future this will change to scope="module", loop_scope="function"?

But this sounds like it will break, according to the docs:

The loop_scope of a fixture can be chosen independently from its caching scope. However, the event loop scope must be larger or the same as the fixture’s caching scope. In other words, it’s possible to reevaluate an async fixture multiple times within the same event loop, but it’s not possible to switch out the running event loop in an async fixture.

Also, according to the migration guide from v0.23:

Explicitly set the loop_scope of async fixtures by replacing occurrences of @pytest.fixture(scope="…") and @pytest_asyncio.fixture(scope="…") with @pytest_asyncio.fixture(loop_scope="…", scope="…") such that loop_scope and scope are the same.

So I'm rather confused



@pytest.fixture(scope='module')
@pytest_asyncio.fixture(scope='module')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you supposed to change these when the fixture doesn't do anything async?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be harmless, but not necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just occurred to me that any @pytest.fixtures added on 8.4.x will likely make it into master unnoticed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.

@oliver-sanders oliver-sanders marked this pull request as draft April 15, 2025 12:11
@oliver-sanders
Copy link
Member Author

oliver-sanders commented Apr 15, 2025

Drafting this whilst we investigate whether there is another way as this is pretty nasty really.

@oliver-sanders oliver-sanders added the BLOCKED This can't happen until something else does label Jun 11, 2025
@oliver-sanders
Copy link
Member Author

pytest-asyncio have now released 1.0.0, strangely our tests seem to pass (I don't understand how), but our event loop scope is presumably being ignored?

Unfortunately, they cut the issue to provide the old behaviour as an option, pushing it back to 1.1.

@oliver-sanders oliver-sanders modified the milestones: 8.5.0, 8.6.0 Jun 18, 2025
@oliver-sanders oliver-sanders modified the milestones: 8.6.0, 8.x Sep 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BLOCKED This can't happen until something else does small
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pytest-asyncio: current version is incompatible with module scoped event loop
2 participants