microsoft / debugpy

An implementation of the Debug Adapter Protocol for Python
https://pypi.org/project/debugpy/
Other
1.74k stars 128 forks source link

FEAT: Support ipython's `_repr_*_()` protocol #1563

Open NickCrews opened 2 months ago

NickCrews commented 2 months ago

I often work with objects that support ipython's _repr_*_() protocol, such as rich objects. For example, Ibis returns rich tables as an output format.

If you run this in an ipython terminal, you get nice output:

from rich.__main__ import make_test_card
make_test_card()

but in the vscode debugger, you just get <rich.table.Table object at 0x13cb9b550>\

Can we use ipython instead of the vanilla interpreter? Or something else to make debugging more ergonomic?

int19h commented 2 months ago

This is an interesting idea, but I don't think this is possible currently, since ultimately debugpy just talks DAP which does not accommodate such things directly. The REPL is implemented by the DAP client sending "evaluate" requests.

So, this would need 1) some additions to the DAP spec to allow for rich content transfer and queries, and 2) VSCode actually supporting them on their end.

Technically it would be possible to do this without protocol support utilizing existing "evaluate" requests if the client just sends expressions like base64.encode(foo.__repr_png__()) etc and then interprets the response data accordingly. It would be Python-specific, however, and VSCode REPL is meant to be generic.

NickCrews commented 2 months ago

thanks for the quick response!

I think I can actually greatly reduce the scope of my feature request. How about instead of supporting ALL of the mimetypes like png, we only support text/plain? If I understand correctly, this is just plain text that 1) DAP should support and 2) vscode could fairly easily support (they don't currently, but I want them to).

I'm imagining this could be an implementation that debugpy uses?

from rich.text import Text

def get_text_plain(obj) -> str | None:
    """Get the text/plain representation of an object, if it has one."""
    try:
        bundles = obj._repr_mimebundle_(include=["text/plain"], exclude=[])
    except AttributeError:
        return None
    return bundles.get("text/plain")

def get_repr(obj) -> str:
    text = get_text_plain(obj)
    if text is not None:
        return text
    return repr(obj)

obj = Text("Hello\nWorld!", style="bold red")
r = get_repr(obj)  # '\x1b[1;31mHello\x1b[0m\n\x1b[1;31mWorld!\x1b[0m\n'
print(r)  # prints to console in bold red
int19h commented 2 months ago

Yeah, that would be pretty straightforward. As it happens I am currently working on object repr in debugpy v2, so I'll just integrate this there.

Coincidentally, do you know if IPython - or anyone else - has a repr protocol that incorporates lazy streaming (rather than computing the whole thing at once)?

NickCrews commented 2 months ago

oops, I think I had a bug in the orig implementation above where "" from get_text_plain() would get interpreted as falsy. It's now fixed with an explicit None check.

Also, if you have any thoughts on the above linked issue they would be appreciated!

Coincidentally, do you know if IPython - or anyone else - has a repr protocol that incorporates lazy streaming (rather than computing the whole thing at once)?

no idea, sorry ;)