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.

classification
Title: inspect.is_decorator_call(frame)
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: BTaskaya, pablogsal, smarie, yselivanov
Priority: normal Keywords:

Created on 2019-04-08 08:21 by smarie, last changed 2022-04-11 14:59 by admin.

Messages (2)
msg339599 - (view) Author: Sylvain Marie (smarie) * Date: 2019-04-08 08:21
Python decorators are frequently proposed by libraries as an easy way to add functionality to user-written functions: see `attrs`, `pytest`, `click`, `marshmallow`, etc.

A common pattern in most such libraries, is that they do not want to provide users with two different symbols for the same function. So they end up implementing decorators that can be used both as decorators (no arguments no parenthesis) AND decorator factories (arguments in parenthesis). This is convenient and intuitive for users. Unfortunately this is not something trivial to implement because the python language does not make any difference between a no-parenthesis decorator call and a with-parenthesis decorator factory call.

So these libraries have to rely on "tricks", the most common one being to check existence of a non-default first parameter that is a callable.

Examples: 

https://github.com/python-attrs/attrs/blob/c2a9dd8e113a0dc72f86490e330f25bc0111971a/src/attr/_make.py#L940

https://github.com/pytest-dev/pytest/blob/13a9d876f74f17907ad04b13132cbd4aa4ad5842/src/_pytest/fixtures.py#L1041

https://github.com/marshmallow-code/marshmallow/blob/ec51dff98999f2189a255fb8bbc22e549e3cc673/src/marshmallow/decorators.py#L161

Implementing these tricks is a bit ugly, but more importantly it is a waste of development time because when one changes his decorators signatures, the trick has to possibly be changed (order of arguments, default values, etc). Therefore it is quite a brake to agile development in the first phase of a project, where the api is not very stable.

I regrouped all known and possible tricks in a library https://github.com/smarie/python-decopatch/ to provide a handy way to solve this problem. But it is still "a bunch of tricks". This library, or the manual implementations such as the examples above, could be much faster/efficient if there were at least, a way to determine if a frame is a call to `@`.

So this is a request to at least have a `inspect.is_decorator_call(frame)` feature in the stdlib. That function would return `True` if the frame is a decorator call using `@`.

Note that a more convenient way to solve this problem is also proposed in https://smarie.github.io/python-decopatch/pep_proposal/#2-preserving-backwards-compatibility : it would be to offer a `@decorator_factory` helper in the stdlib. But first feedback from python-ideas mailing list showed that this was maybe too disruptive :)
msg354385 - (view) Author: Sylvain Marie (smarie) * Date: 2019-10-10 14:46
Quick update on this feature: for the following example to work:

from inspect import is_decorator_call

def set_hello_tag(tag='world'):
    if is_decorator_call():
        # called without parenthesis!
        # the decorated object is `tag`
        return set_hello_tag()(tag)   # note that `is_decorator_call` should not return True for this call
    else:
        def decorate(f):
            setattr(f, 'hello', tag)  # set a hello tag on the decorated f
            return f
        return decorate


Then `is_decorator_call` should be callable without arguments (default to current frame) and should return `True` only if the current frame is the one directly following decorator application. In nested frames (such as the one obtained after first recursive call to `set_hello_tag` above, `is_decorator_call` should return `False`.
History
Date User Action Args
2022-04-11 14:59:13adminsetgithub: 80734
2020-01-07 08:26:54BTaskayasetnosy: + BTaskaya
2019-10-10 22:10:20pablogsalsetnosy: + pablogsal
2019-10-10 14:46:24smariesetmessages: + msg354385
2019-04-08 09:59:09xtreaksetnosy: + yselivanov

versions: + Python 3.8
2019-04-08 08:21:55smariecreate