This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author njs
Recipients John Belmonte, asvetlov, ncoghlan, njs, xtreak, yselivanov
Date 2019-07-25.06:40:42
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1564036843.36.0.274851809798.issue37398@roundup.psfhosted.org>
In-reply-to
Content
There are two different axes of sync/async here, and it's important not to mix them up: the context manager could be sync or async, and the function that's being decorated could be sync or async. This issue is about the case where you have a sync context manager, and it's decorating an async function.

The current behavior is definitely super confusing and not at all useful.

In general, I'm also very skeptical about using iscoroutinefunction to automatically change semantics, because of the issue Andrew mentions about failing to handle complicated objects like functools.partial instances or sync functions that return awaitables. In this *specific* case though, I'm not sure... maybe it actually is OK?

What's special about this case is that when you write:

@sync_cm()
async def foo():
    ...

...the same person is writing both the @ line and the 'async def' line, and they're right next to each other in the source code. So the normal concern about some unexpected foreign object being passed in doesn't really apply. And in fact, if I did have an awaitable-returning-function:

@sync_cm()
def foo():
    return some_async_fn()

...then I think most people would actually *expect* this to be equivalent to:

def foo():
    with sync_cm():
        return some_async_fn()

Not very useful, but not surprising either. So maybe this is the rare case where switching on iscoroutinefunction is actually OK? The only cases where it would fail is if someone invokes the decorator form directly, e.g.

new_fn = sync_cm()(old_fn)

This seems pretty rare, but probably someone does do it, so I dunno.

If we think that's too magic, another option would be to make up some more explicit syntax, like:

@sync_cm().wrap_async
async def foo(): ...

...Oh, but I think this breaks the Guido's gut feeling rule (https://mail.python.org/pipermail/python-dev/2004-August/046711.html), so it would have to instead be:

@sync_cm.wrap_async()
async def foo(): ...

And even if we decide not to automatically switch on iscoroutinefunction, we should probably add a warning or something at least, because right now

@sync_cm()
async def ...

is definitely a mistake.

(Adding Nick to nosy as the contextlib maintainer.)
History
Date User Action Args
2019-07-25 06:40:43njssetrecipients: + njs, ncoghlan, asvetlov, yselivanov, xtreak, John Belmonte
2019-07-25 06:40:43njssetmessageid: <1564036843.36.0.274851809798.issue37398@roundup.psfhosted.org>
2019-07-25 06:40:43njslinkissue37398 messages
2019-07-25 06:40:42njscreate