Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorators that change docstrings are not accounted for #117

Closed
blink1073 opened this issue Jan 28, 2013 · 16 comments
Closed

Decorators that change docstrings are not accounted for #117

blink1073 opened this issue Jan 28, 2013 · 16 comments
Labels

Comments

@blink1073
Copy link
Contributor

The following behavior was observed using the latest dev branch a86cfa2.

The following code prints old instead of new, meaning that the decorator was not accounted for.

import jedi
code = """
def wrap(func):
    func.__doc__ = 'new'
    return func

@wrap
def orig():
    '''old'''
    pass

orig("""
script = jedi.Script(code, 11, 6, None)
print script.get_in_function_call().docstring()

Result:

'orig()\n\nold'

UPDATE: The following now works:

import jedi
code = """import functools; functools.partial("""
script = jedi.Script(code, 1, len(code), None)
print script.get_in_function_call().docstring()

Result:

'partial(func, *args, **keywords) - new function with partial application\nof the given arguments and keywords.'
@dbrgn
Copy link
Collaborator

dbrgn commented Jul 28, 2014

Apparently the codebase has changed a bit. This is a test that works right now:

import jedi
code = """
def wrap(func):
    func.__doc__ = 'new'
    return func

@wrap
def orig():
    '''old'''
    pass

orig("""
script = jedi.Script(code, 11, 5, None)
ifc = script.get_in_function_call()
ifc.docstring()

Result:

'orig()\n\nold'

@blink1073
Copy link
Contributor Author

Thank @dbrgn, I updated the issue and noted that functools.partial now works.

@davidhalter
Copy link
Owner

The other thing will definitely not just start "magically" working.

@blink1073
Copy link
Contributor Author

Don't sell yourself short David, you are a Jedi master after all.

@dbrgn
Copy link
Collaborator

dbrgn commented Jul 29, 2014

Jedis aren't what they used to be anymore :(

Maybe using the --force parameter will help ;)

@davidhalter
Copy link
Owner

Since I'm working on this kind of stuff now, I'm closing this in favor of #503. That issue is essentially the same than this one, but it is just about wraps, which is the remaining problem AFAIK.

Changing arbitrary docstrings is definitely not going to be supported in the future, however changing docstrings with wraps is going to be supported.

@blink1073
Copy link
Contributor Author

Works for me, thanks @davidhalter! ♥️

@davidhalter
Copy link
Owner

It's been a few years since you wrote this issue, haha. :)

@blink1073
Copy link
Contributor Author

Any and all work you do here is much appreciated my friend

@jsh9
Copy link

jsh9 commented Mar 20, 2020

Hello @davidhalter , I am encountering an issue with jedi (version 0.15.2).
This ticket here (spyder-ide/spyder#11150) provides all the details. Basically, Spyder relies on jedi to parse the docstring, and when a function is decorated with a decorator, this function's docstring cannot be correctly parsed.

@davidhalter
Copy link
Owner

Please use 0.16.0 and try again.

@jsh9
Copy link

jsh9 commented Mar 21, 2020

I upgraded jedi to 0.16.0 (and parso is on 0.6.2), and still saw the same problem. (I used Spyder to do the checking, because the example code above seemed outdated.)

Here is the script that I used:

import functools
import time

from typeguard import typechecked

@typechecked
def func(a: int):
    """
    A function

    Parameters
    ----------

    a : int
        Something.
    """
    print('someting')


def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer

@timer
def waste_some_time(num_times):
    """
    Waste some time

    Parameters
    ----------
    num_times : TYPE
        DESCRIPTION.

    Returns
    -------
    None.

    """
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])

b = func(3)  # Spyder is unable to retrieve its docstring
waste_some_time(1234)  # Spyder is able to retrieve its docstring

The docstring of the function func cannot be retrieved by jedi, because it is decorated by the typechecked() function of typeguard (https://github.com/agronholm/typeguard/blob/master/typeguard/__init__.py#L761).

It seems that it is something about typechecked() that makes jedi stop working. I cannot figure out what that is. Could you look into it?

Thanks!

@davidhalter
Copy link
Owner

The docstring I get is

FunctionType(*args: Any, **kwargs: Any) -> Any

A function

Parameters
----------

a : int
    Something.

This is probably expected. The signature seems wrong, but I'm not really sure it is, because there are overload definitions end in a callable, so that seems also correct. Can you give me more pointers on what's wrong according to you?

If you create a test script that helps a lot to reproduce. It also helps if you provide me with the actual output.

@davidhalter
Copy link
Owner

I'm realizing that there's something wrong with Script.infer. This is not the way how you should be getting docstrings. So if spyder does this, it's wrong. They should be using the Script.help function.

However I found the issue and fixed it.

@jsh9
Copy link

jsh9 commented Mar 21, 2020

Hi @davidhalter :

Spyder was not able to extract the docstring of func() like you were able to -- Spyder simply shows "no docstring available" for func(). I relied on Spyder to do so, because I don't know how to extract docstrings outside of Spyder (if you can show me a way, I'd appreciate it).

Do still I need to ping Spyder's authors to fix the usage of Spyder.infer, or does your fix (a2f4d1b) solve the issue altogether?

Thanks!

@davidhalter
Copy link
Owner

I relied on Spyder to do so, because I don't know how to extract docstrings outside of Spyder (if you can show me a way, I'd appreciate it).

It's as simple as:

import jedi
name, = jedi.Script(path='file.py').infer(line=2, column=3)
name.docstring()

Jedi's documentation is also available under https://jedi.readthedocs.org.

Do still I need to ping Spyder's authors to fix the usage of Spyder.infer, or does your fix (a2f4d1b) solve the issue altogether?

No it's fine. They use a language server implementation that probably does quite a few things wrong. I'll do the talking at some point when Jedi 1.0 is out :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants