Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit97c682f

Browse files
bcmillsseifertm
authored andcommitted
Copy context variables from non-generator fixtures
1 parent62ab185 commit97c682f

File tree

2 files changed

+80
-44
lines changed

2 files changed

+80
-44
lines changed

‎pytest_asyncio/plugin.py

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -327,18 +327,7 @@ async def setup():
327327
setup_task=_create_task_in_context(event_loop,setup(),context)
328328
result=event_loop.run_until_complete(setup_task)
329329

330-
# Copy the context vars set by the setup task back into the ambient
331-
# context for the test.
332-
context_tokens= []
333-
forvarincontext:
334-
try:
335-
ifvar.get()iscontext.get(var):
336-
# Not modified by the fixture, so leave it as-is.
337-
continue
338-
exceptLookupError:
339-
pass
340-
token=var.set(context.get(var))
341-
context_tokens.append((var,token))
330+
reset_contextvars=_apply_contextvar_changes(context)
342331

343332
deffinalizer()->None:
344333
"""Yield again, to finalize."""
@@ -355,38 +344,15 @@ async def async_finalizer() -> None:
355344

356345
task=_create_task_in_context(event_loop,async_finalizer(),context)
357346
event_loop.run_until_complete(task)
358-
359-
# Since the fixture is now complete, restore any context variables
360-
# it had set back to their original values.
361-
whilecontext_tokens:
362-
(var,token)=context_tokens.pop()
363-
var.reset(token)
347+
ifreset_contextvarsisnotNone:
348+
reset_contextvars()
364349

365350
request.addfinalizer(finalizer)
366351
returnresult
367352

368353
fixturedef.func=_asyncgen_fixture_wrapper# type: ignore[misc]
369354

370355

371-
def_create_task_in_context(loop,coro,context):
372-
"""
373-
Return an asyncio task that runs the coro in the specified context,
374-
if possible.
375-
376-
This allows fixture setup and teardown to be run as separate asyncio tasks,
377-
while still being able to use context-manager idioms to maintain context
378-
variables and make those variables visible to test functions.
379-
380-
This is only fully supported on Python 3.11 and newer, as it requires
381-
the API added for https://github.com/python/cpython/issues/91150.
382-
On earlier versions, the returned task will use the default context instead.
383-
"""
384-
try:
385-
returnloop.create_task(coro,context=context)
386-
exceptTypeError:
387-
returnloop.create_task(coro)
388-
389-
390356
def_wrap_async_fixture(fixturedef:FixtureDef)->None:
391357
fixture=fixturedef.func
392358

@@ -403,11 +369,23 @@ async def setup():
403369
res=awaitfunc(**_add_kwargs(func,kwargs,event_loop,request))
404370
returnres
405371

406-
# Since the fixture doesn't have a cleanup phase, if it set any context
407-
# variables we don't have a good way to clear them again.
408-
# Instead, treat this fixture like an asyncio.Task, which has its own
409-
# independent Context that doesn't affect the caller.
410-
returnevent_loop.run_until_complete(setup())
372+
context=contextvars.copy_context()
373+
setup_task=_create_task_in_context(event_loop,setup(),context)
374+
result=event_loop.run_until_complete(setup_task)
375+
376+
# Copy the context vars modified by the setup task into the current
377+
# context, and (if needed) add a finalizer to reset them.
378+
#
379+
# Note that this is slightly different from the behavior of a non-async
380+
# fixture, which would rely on the fixture author to add a finalizer
381+
# to reset the variables. In this case, the author of the fixture can't
382+
# write such a finalizer because they have no way to capture the Context
383+
# in which the setup function was run, so we need to do it for them.
384+
reset_contextvars=_apply_contextvar_changes(context)
385+
ifreset_contextvarsisnotNone:
386+
request.addfinalizer(reset_contextvars)
387+
388+
returnresult
411389

412390
fixturedef.func=_async_fixture_wrapper# type: ignore[misc]
413391

@@ -432,6 +410,57 @@ def _get_event_loop_fixture_id_for_async_fixture(
432410
returnevent_loop_fixture_id
433411

434412

413+
def_create_task_in_context(loop,coro,context):
414+
"""
415+
Return an asyncio task that runs the coro in the specified context,
416+
if possible.
417+
418+
This allows fixture setup and teardown to be run as separate asyncio tasks,
419+
while still being able to use context-manager idioms to maintain context
420+
variables and make those variables visible to test functions.
421+
422+
This is only fully supported on Python 3.11 and newer, as it requires
423+
the API added for https://github.com/python/cpython/issues/91150.
424+
On earlier versions, the returned task will use the default context instead.
425+
"""
426+
try:
427+
returnloop.create_task(coro,context=context)
428+
exceptTypeError:
429+
returnloop.create_task(coro)
430+
431+
432+
def_apply_contextvar_changes(
433+
context:contextvars.Context,
434+
)->Callable[[],None]|None:
435+
"""
436+
Copy contextvar changes from the given context to the current context.
437+
438+
If any contextvars were modified by the fixture, return a finalizer that
439+
will restore them.
440+
"""
441+
context_tokens= []
442+
forvarincontext:
443+
try:
444+
ifvar.get()iscontext.get(var):
445+
# This variable is not modified, so leave it as-is.
446+
continue
447+
exceptLookupError:
448+
# This variable isn't yet set in the current context at all.
449+
pass
450+
token=var.set(context.get(var))
451+
context_tokens.append((var,token))
452+
453+
ifnotcontext_tokens:
454+
returnNone
455+
456+
defrestore_contextvars():
457+
whilecontext_tokens:
458+
(var,token)=context_tokens.pop()
459+
var.reset(token)
460+
461+
returnrestore_contextvars
462+
463+
435464
classPytestAsyncioFunction(Function):
436465
"""Base class for all test functions managed by pytest-asyncio."""
437466

‎tests/async_fixtures/test_async_fixtures_contextvars.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,16 @@ async def var_fixture_3(var_fixture_2):
5858
yield
5959

6060

61+
@pytest.fixture(scope="function")
62+
asyncdefvar_fixture_4(var_fixture_3,request):
63+
assert_context_var.get()=="value3"
64+
_context_var.set("value4")
65+
# Rely on fixture teardown to reset the context var.
66+
67+
6168
@pytest.mark.asyncio
6269
@pytest.mark.xfail(
6370
sys.version_info< (3,11),reason="requires asyncio Task context support"
6471
)
65-
asyncdeftest(var_fixture_3):
66-
assert_context_var.get()=="value3"
72+
asyncdeftest(var_fixture_4):
73+
assert_context_var.get()=="value4"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp