Skip to content

Python: Safely and efficiently compute variable display values #8545

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

Merged
merged 6 commits into from
Jul 18, 2025

Conversation

seeM
Copy link
Contributor

@seeM seeM commented Jul 16, 2025

Addresses #8245. The approach is inspired by pydevd which backs the debugger used by most Python IDEs today.

The focus is on safely (i.e. no hangs) and efficiently calculating and sending display values over the wire, not on perfectly formatting those display values, hence the large max character values. The idea is that further formatting would be handled in the UI, although we can change this if needed.

I've mostly left the display formatting of the larger third party objects as is (numpy, pandas, etc). The lower level changes here should make refinements to those easier in future.

Release Notes

New Features

  • N/A

Bug Fixes

QA Notes

See the repro steps in #8245 as well as the new unit tests.

@:variables @:data-explorer @:console

Copy link

github-actions bot commented Jul 16, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical @:variables @:data-explorer @:console

readme  valid tags

austin3dickey
austin3dickey previously approved these changes Jul 16, 2025
Copy link
Contributor

@austin3dickey austin3dickey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fantastic, thanks!

"pandas.core.indexes.range.RangeIndex": "pandas.RangeIndex",
"pandas.core.indexes.multi.MultiIndex": "pandas.MultiIndex",
# Just display Int64Index as pandas.Index, since the former is deprecated since pandas v1.4.0.
"pandas.core.indexes.numeric.Int64Index": "pandas.Index",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

@@ -165,10 +174,10 @@ def get_size(self) -> int:
def has_children(self) -> bool:
return self.get_length() > 0

def has_child(self, _key: Any) -> bool:
def has_child(self, key: Any) -> bool: # noqa: ARG002
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this makes more sense


# If the string fits the limit, return it as is.
# NOTE: We check the actual string length but return the repr which may differ e.g.
# some characters may be escaped.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we don't check len(repr(value))?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that is actually related to the bug fixed in this PR, since repr(value) could end up duplicating a huge string. I'll add that to the note

@@ -225,6 +226,39 @@ def test_change_detection_over_limit(shell: PositronShell, variables_comm: Dummy
_assert_assigned(shell, big_array, varname, variables_comm, "unevaluated")


def test_change_detection_run_cell_performance(shell: PositronShell, variables_comm) -> None: # noqa: ARG001 need the variables comm to be connected but don't actually use it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can get around this noqa by using @pytest.mark.usefixtures("variables_comm") instead!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's way nicer, thanks!

# Also check absolute time - should complete within 1 second.
assert large_object_ns < 1e9, (
f"Simple cell execution took {large_object_ns / 1e6:.3f}ms with large object in namespace"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this test :)

isabelizimm
isabelizimm previously approved these changes Jul 17, 2025
Copy link
Contributor

@isabelizimm isabelizimm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two small things, but this feels super snappy!

# For items that are the same object as the parent, avoid infinite recursion
# and represent the item with ellipsis.
if item is value:
item_repr, item_truncated = f"{prefix}{ELLIPSIS}{suffix}", True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-07-17 at 10 15 52 AM

not needed for this PR, but this can get you in a weird state with double ellipses if you end up resizing the variables pane

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. It does look like an existing issue on main. It isn't technically wrong but I agree it's not ideal. I also feel like I want to double-click it to expand the display value somehow.

image

…ors.py

Co-authored-by: Isabel Zimmerman <[email protected]>
Signed-off-by: Wasim Lorgat <[email protected]>
@seeM seeM merged commit 06723e1 into main Jul 18, 2025
29 of 30 checks passed
@seeM seeM deleted the bugfix/display-value-perf branch July 18, 2025 18:36
@github-actions github-actions bot locked and limited conversation to collaborators Jul 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants