diff --git a/extensions/positron-python/pythonFiles/positron/environment.py b/extensions/positron-python/pythonFiles/positron/environment.py index c88f773646b..69e27edce5c 100644 --- a/extensions/positron-python/pythonFiles/positron/environment.py +++ b/extensions/positron-python/pythonFiles/positron/environment.py @@ -8,7 +8,7 @@ from collections.abc import Iterable, Mapping, Sequence from typing import Any, Optional -from .inspectors import get_inspector, is_inspectable +from .inspectors import get_inspector, is_inspectable, MAX_ITEMS from .utils import get_qualname @@ -165,9 +165,6 @@ def __init__(self, message): self["message"] = message -MAX_ITEMS = 2000 - - class EnvironmentService: def __init__(self, kernel): # noqa: F821 self.kernel = kernel @@ -403,29 +400,9 @@ def _send_details(self, path: Sequence, context: Any = None): """ children = [] - if isinstance(context, Mapping): - # Treat dictionary items as children - children.extend(self._summarize_variables(context)) - - elif is_inspectable(context): - inspector = get_inspector(context) - for child_name in inspector.get_child_names(context): - child_display_type, child_value = inspector.get_child_info(context, child_name) - summary = self._summarize_variable(child_name, child_value) - if summary is not None: - summary["display_type"] = child_display_type - children.append(summary) - - elif isinstance(context, (list, set, frozenset, tuple)): - # Treat collection items as children, with the index as the name - for i, item in enumerate(context): - if len(children) >= MAX_ITEMS: - break - - summary = self._summarize_variable(i, item) - if summary is not None: - children.append(summary) - + inspector = get_inspector(context) + if inspector is not None and inspector.has_children(context): + children = inspector.summarize_children(context, self._summarize_variable) else: # Otherwise, treat as a simple value at given path summary = self._summarize_variable("", context) diff --git a/extensions/positron-python/pythonFiles/positron/inspectors.py b/extensions/positron-python/pythonFiles/positron/inspectors.py index bb4003da81d..a9a159a1ce8 100644 --- a/extensions/positron-python/pythonFiles/positron/inspectors.py +++ b/extensions/positron-python/pythonFiles/positron/inspectors.py @@ -10,97 +10,105 @@ import types import uuid from collections.abc import Mapping, Sequence, Set -from typing import Any, Optional, Tuple +from typing import Any, Callable, Optional, Tuple from .dataviewer import DataColumn, DataSet from .utils import get_value_length, get_qualname, pretty_format +MAX_ITEMS: int = 2000 TRUNCATE_AT: int = 1024 PRINT_WIDTH: int = 100 +# Marker used to track if our default object was returned from a +# conditional property lookup +__POSITRON_DEFAULT__ = object() + # Base inspector for any type class PositronInspector: def get_display_value( self, - value, + value: Any, print_width: Optional[int] = PRINT_WIDTH, truncate_at: Optional[int] = TRUNCATE_AT, ) -> Tuple[str, bool]: return pretty_format(value, print_width, truncate_at) - def get_display_type(self, value) -> str: + def get_display_type(self, value: Any) -> str: if value is not None: type_name = type(value).__name__ - length = self.get_length(value) display_type = type_name if isinstance(value, str): - # For strings, which are sequences, we suppress showing - # the length in the type display - return type_name + # For strings, which are also Sequences, we suppress + # showing the length in the display type + display_type = type_name + else: + length = self.get_length(value) - elif isinstance(value, Set): - display_type = f"{type_name} {{{length}}}" + # Also display length for various collections and maps + # using the Python notation for the type + if isinstance(value, Set): + display_type = f"{type_name} {{{length}}}" - elif isinstance(value, tuple): - display_type = f"{type_name} ({length})" + elif isinstance(value, tuple): + display_type = f"{type_name} ({length})" - elif isinstance(value, (Sequence, Mapping)): - display_type = f"{type_name} [{length}]" + elif isinstance(value, (Sequence, Mapping)): + display_type = f"{type_name} [{length}]" - elif length > 0: - display_type = f"{type_name} [{length}]" + else: + if length > 0: + display_type = f"{type_name} [{length}]" else: display_type = "NoneType" return display_type - def get_kind(self, value) -> str: - kind = _get_kind(value) - if kind is None: - kind = "other" - return kind + def get_kind(self, value: Any) -> str: + return _get_kind(value) - def get_type_info(self, value) -> str: + def get_type_info(self, value: Any) -> str: return get_qualname(value) - def get_length(self, value) -> int: + def get_length(self, value: Any) -> int: return get_value_length(value) - def get_size(self, value) -> int: + def get_size(self, value: Any) -> int: return sys.getsizeof(value) - def has_children(self, value) -> bool: - return self.get_length(value) > 0 - - def has_child(self, value, child_name) -> bool: + def has_children(self, value: Any) -> bool: return False - def has_viewer(self, value) -> bool: + def has_child(self, value: Any, child_name: str) -> bool: return False - def get_child_names(self, value) -> list: + def get_child(self, value: Any, child_name: str) -> Any: + return None + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: return [] - def get_child_info(self, value, child_name) -> Tuple[str, Any]: - return ("", []) + def has_viewer(self, value: Any) -> bool: + return False - def equals(self, value1, value2) -> bool: + def equals(self, value1: Any, value2: Any) -> bool: return value1 == value2 - def copy(self, value) -> Any: + def copy(self, value: Any) -> Any: return copy.copy(value) - def to_dataset(self, value, title: str) -> Optional[DataSet]: + def to_dataset(self, value: Any, title: str) -> Optional[DataSet]: return None - def to_html(self, value) -> str: + def to_html(self, value: Any) -> str: return repr(value) - def to_tsv(self, value) -> str: + def to_tsv(self, value: Any) -> str: return repr(value) @@ -108,32 +116,48 @@ def to_tsv(self, value) -> str: class BooleanInspector(PositronInspector): - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "boolean" - def has_children(self, value) -> bool: - return False - class BytesInspector(PositronInspector): def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: - # Ignore print_width for bytes + # Ignore print_width for bytes types return pretty_format(value, None, truncate_at) - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "bytes" - def has_children(self, value) -> bool: - return False - class CollectionInspector(PositronInspector): - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "collection" - def has_children(self, value) -> bool: + def has_child(self, value: Any, child_name: str) -> bool: + if isinstance(value, (list, tuple, range)): + try: + index = int(child_name) + return index < self.get_length(value) + except Exception: + logging.warning(f"Unable to find child value at '{child_name}'", exc_info=True) + + return False + + def get_child(self, value: Any, child_name: str) -> Any: + child_value = None + + if isinstance(value, (list, tuple, range)): + try: + index = int(child_name) + child_value = value[index] + except Exception: + logging.warning(f"Unable to find child value at '{child_name}'", exc_info=True) + + return child_value + + def has_children(self, value: Any) -> bool: # For ranges, we don't visualize the children as they're # implied as a contiguous set of integers in a range # For sets, we don't visualize the children as they're @@ -141,12 +165,28 @@ def has_children(self, value) -> bool: if isinstance(value, (frozenset, range, set)): return False else: - return super().has_children(value) + return self.get_length(value) > 0 + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: + # Treat collection items as children, with the index as the name + children = [] + if isinstance(value, (list, tuple)): + for i, item in enumerate(value): + if len(children) >= MAX_ITEMS: + break + + summary = summarizer(str(i), item) + if summary is not None: + children.append(summary) + + return children class FunctionInspector(PositronInspector): def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: if callable(value): sig = inspect.signature(value) @@ -154,49 +194,113 @@ def get_display_value( sig = "()" return (f"{value.__qualname__}{sig}", False) - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "function" - def has_children(self, value) -> bool: - return False - class MapInspector(PositronInspector): - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "map" + def has_children(self, value: Any) -> bool: + return self.get_length(value) > 0 -class NumberInspector(PositronInspector): - def get_kind(self, value) -> str: - return "number" + def has_child(self, value: Any, child_name: str) -> bool: + if isinstance(value, Mapping): + map_value = value.get(child_name, __POSITRON_DEFAULT__) + return map_value is not __POSITRON_DEFAULT__ - def has_children(self, value) -> bool: return False + def get_child(self, value: Any, child_name: str) -> Any: + child_value = None + + if isinstance(value, Mapping): + map_value = value.get(child_name, __POSITRON_DEFAULT__) + if map_value is not __POSITRON_DEFAULT__: + child_value = map_value + + return child_value + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: + children = [] + + if isinstance(value, Mapping): + for key, value in value.items(): + if len(children) >= MAX_ITEMS: + break + + summary = summarizer(str(key), value) + if summary is not None: + children.append(summary) + + return children + + +class NumberInspector(PositronInspector): + def get_kind(self, value: Any) -> str: + return "number" + class StringInspector(PositronInspector): def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: # Ignore print_width for strings display_value, is_truncated = super().get_display_value(value, None, truncate_at) + + # Use repr() to show quotes around strings return repr(display_value), is_truncated - def get_kind(self, value) -> str: + def get_kind(self, value: Any) -> str: return "string" - def has_children(self, value) -> bool: - return False - class TableInspector(PositronInspector): - def get_kind(self, value) -> str: - if value is not None: - return "table" - else: - return "empty" + """ + Base inspector for tabular data + """ + + def get_kind(self, value: Any) -> str: + return "table" + + def has_children(self, value: Any) -> bool: + return self.get_length(value) > 0 + + def has_child(self, value: Any, child_name: str) -> bool: + return child_name in self.get_column_names(value) + + def get_child(self, value: Any, child_name: str) -> Any: + return self.get_column(value, child_name) - def has_viewer(self, value) -> bool: + def get_column_names(self, value: Any) -> list: + return [] + + def get_column(self, value: Any, column_name: str) -> list: + return [] + + def get_column_display_type(self, value: Any, column_name: str) -> str: + return "" + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: + children = [] + + for column_name in self.get_column_names(value): + column_value = self.get_column(value, column_name) + column_display_type = self.get_column_display_type(value, column_name) + + summary = summarizer(column_name, column_value) + if summary is not None: + summary["display_type"] = column_display_type + children.append(summary) + + return children + + def has_viewer(self, value: Any) -> bool: return True @@ -207,56 +311,77 @@ class PandasDataFrameInspector(TableInspector): CLASS_QNAME = "pandas.core.frame.DataFrame" def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: - type_name = type(value).__name__ - shape = value.shape - return (f"{type_name}: [{shape[0]} rows x {shape[1]} columns]", True) + display_value = get_qualname(value) + + if hasattr(value, "shape"): + shape = value.shape + display_value = f"[{shape[0]} rows x {shape[1]} columns] {display_value}" - def get_display_type(self, value) -> str: + return (display_value, True) + + def get_display_type(self, value: Any) -> str: display_type = type(value).__name__ - shape = value.shape - display_type = display_type + f" [{shape[0]}x{shape[1]}]" + + if hasattr(value, "shape"): + shape = value.shape + display_type = f"{display_type} [{shape[0]}x{shape[1]}]" + return display_type - def get_child_names(self, value) -> list: + def get_length(self, value: Any) -> int: + return value.shape[0] + + def get_column_names(self, value: Any) -> list: try: return value.columns.values.tolist() except Exception: return [] - def has_child(self, value, child_name) -> bool: - return child_name in self.get_child_names(value) - - def get_child_info(self, value, child_name) -> Tuple[str, Any]: + def get_column(self, value: Any, column_name: str) -> Any: try: - column = value[child_name] - display_type = type(column).__name__ + column = value[column_name] values = column.values.tolist() + except Exception: + values = [] + logging.warning("Unable to get Pandas column: %s", column_name, exc_info=True) + + return values + + def get_column_display_type(self, value: Any, column_name: str) -> str: + try: + column = value[column_name] + + # Use dtype information, if we have it + if hasattr(column, "dtype"): + column_type = str(column.dtype) + else: + column_type = type(column).__name__ - # Include size information if we have it + # Include size information, if we have it if hasattr(column, "size"): size = column.size else: + values = column.values.tolist() size = len(values) - display_type = f"{display_type} [{size}]" + display_type = f"{column_type} [{size}]" except Exception: display_type = "" - values = [] - logging.warning("Unable to get Pandas child: %s", child_name, exc_info=True) + logging.warning("Unable to get Pandas column type: %s", column_name, exc_info=True) - return (display_type, values) + return display_type - def equals(self, value1, value2) -> bool: + def equals(self, value1: Any, value2: Any) -> bool: return value1.equals(value2) - def copy(self, value) -> Any: + def copy(self, value: Any) -> Any: return value.copy() - def to_dataset(self, value, title: str) -> Optional[DataSet]: + def to_dataset(self, value: Any, title: str) -> Optional[DataSet]: columns = [] - for column_name in self.get_child_names(value): + for column_name in self.get_column_names(value): column = value[column_name] column_type = type(column).__name__ column_data = column.values.tolist() @@ -264,57 +389,87 @@ def to_dataset(self, value, title: str) -> Optional[DataSet]: return DataSet(str(uuid.uuid4()), title, columns) - def to_html(self, value) -> str: + def to_html(self, value: Any) -> str: return value.to_html() - def to_tsv(self, value) -> str: + def to_tsv(self, value: Any) -> str: return value.to_csv(path_or_buf=None, sep="\t") -class PandasSeriesInspector(TableInspector): +class PandasSeriesInspector(CollectionInspector): CLASS_QNAME = "pandas.core.series.Series" def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: - return (str(value), True) + try: + display_value = value.to_string(index=False, max_rows=MAX_ITEMS) + return (display_value, True) + except Exception: + logging.warning("Unable to display Pandas Series", exc_info=True) + display_value = self.get_display_type(value) + return (display_value, True) - def get_display_type(self, value) -> str: + def get_display_type(self, value: Any) -> str: display_type = type(value).__name__ length = len(value) display_type = display_type + f" [{length}]" return display_type - def get_child_names(self, value) -> list: + def get_length(self, value: Any) -> int: + return value.size + + def has_child(self, value: Any, child_name: str) -> bool: try: - return list(map(str, list(range(value.size)))) + index = int(child_name) + return index < self.get_length(value) except Exception: - return [] + logging.warning(f"Unable to find Pandas Series child at '{child_name}'", exc_info=True) - def has_child(self, value, child_name) -> bool: - return child_name in self.get_child_names(value) + return False - def get_child_info(self, value, child_name) -> Tuple[str, Any]: + def get_child(self, value: Any, child_name: str) -> Any: + child_value = None + + try: + index = int(child_name) + child_value = value.iat[index] + except Exception: + logging.warning(f"Unable to find Pandas Series child at '{child_name}'", exc_info=True) + + return child_value + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: + # Treat collection items as children, with the index as the name + children = [] try: - item = value.iat[int(child_name)] - display_type = type(item).__name__ - return (display_type, item) + items = value.to_list() + for i, item in enumerate(items): + if len(children) >= MAX_ITEMS: + break + + summary = summarizer(str(i), item) + if summary is not None: + children.append(summary) except Exception: - logging.warning("Unable to get Series child: %s", child_name, exc_info=True) - return ("unknown", []) + logging.warning("Error summarizing Pandas Series children", exc_info=True) + + return children - def equals(self, value1, value2) -> bool: + def equals(self, value1: Any, value2: Any) -> bool: return value1.equals(value2) - def copy(self, value) -> Any: + def copy(self, value: Any) -> Any: return value.copy() - def to_html(self, value) -> str: + def to_html(self, value: Any) -> str: # TODO: Support HTML return self.to_tsv(value) - def to_tsv(self, value) -> str: + def to_tsv(self, value: Any) -> str: return value.to_csv(path_or_buf=None, sep="\t") @@ -322,46 +477,75 @@ class PolarsInspector(TableInspector): CLASS_QNAME = "polars.dataframe.frame.DataFrame" def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: - type_name = type(value).__name__ - shape = value.shape - return (f"{type_name}: [{shape[0]} rows x {shape[1]} columns]", True) + display_value = get_qualname(value) + + if hasattr(value, "shape"): + shape = value.shape + display_value = f"[{shape[0]} rows x {shape[1]} columns] {display_value}" + + return (display_value, True) - def get_display_type(self, value) -> Tuple[int, int]: + def get_display_type(self, value: Any) -> str: display_type = type(value).__name__ - shape = value.shape - display_type = display_type + f" [{shape[0]}x{shape[1]}]" + + if hasattr(value, "shape"): + shape = value.shape + display_type = f"{display_type} [{shape[0]}x{shape[1]}]" return display_type - def get_child_names(self, value) -> list: + def get_length(self, value: Any) -> int: + return value.shape[0] + + def get_column_names(self, value: Any) -> list: try: return value.columns except Exception: return [] - def has_child(self, value, child_name) -> bool: - return child_name in self.get_child_names(value) - - def get_child_info(self, value, child_name) -> Tuple[str, Any]: + def get_column(self, value: Any, child_name: str) -> Any: try: column = value.get_column(child_name) - display_type = type(column).__name__ - return (display_type, column.to_list()) + return column.to_list() except Exception: logging.warning("Unable to get Polars child: %s", child_name, exc_info=True) - return ("unknown", []) + return [] + + def get_column_display_type(self, value: Any, column_name: str) -> str: + try: + column = value.get_column(column_name) - def equals(self, value1, value2) -> bool: + # Use dtype information, if we have it + if hasattr(column, "dtype"): + column_type = str(column.dtype) + else: + column_type = type(column).__name__ + + # Include size information, if we have it + if hasattr(column, "len"): + size = column.len() + else: + values = column.to_list() + size = len(values) + + display_type = f"{column_type} [{size}]" + except Exception: + logging.warning("Unable to get Polars column type: %s", column_name, exc_info=True) + display_type = "" + + return display_type + + def equals(self, value1: Any, value2: Any) -> bool: return value1.frame_equal(value2) - def copy(self, value) -> Any: + def copy(self, value: Any) -> Any: return value.clone() - def to_dataset(self, value, title: str) -> Optional[DataSet]: + def to_dataset(self, value: Any, title: str) -> Optional[DataSet]: columns = [] - for column_name in self.get_child_names(value): + for column_name in self.get_column_names(value): column = value.get_column(column_name) column_type = type(column).__name__ column_data = column.to_list() @@ -369,47 +553,102 @@ def to_dataset(self, value, title: str) -> Optional[DataSet]: return DataSet(str(uuid.uuid4()), title, columns) - def to_html(self, value) -> str: + def to_html(self, value: Any) -> str: return value._repr_html_() - def to_tsv(self, value) -> str: + def to_tsv(self, value: Any) -> str: return value.write_csv(file=None, separator="\t") -class NumpyNdarrayInspector(TableInspector): +class NumpyNdarrayInspector(CollectionInspector): CLASS_QNAME = "numpy.ndarray" def get_display_value( - self, value, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT + self, value: Any, print_width: int = PRINT_WIDTH, truncate_at: int = TRUNCATE_AT ) -> Tuple[str, bool]: - return (str(value), False) + try: + import numpy as np - def get_display_type(self, value) -> str: - display_type = type(value).__name__ - length = len(value) - display_type = display_type + f" [{length}]" + return ( + np.array2string( + value, max_line_width=PRINT_WIDTH, threshold=20, edgeitems=9, separator="," + ), + True, + ) + except Exception: + logging.warning("Unable to display Ndarray", exc_info=True) + return (self.get_display_type(value), True) + + def get_display_type(self, value: Any) -> str: + # Use dtype information, if we have it + if hasattr(value, "dtype"): + display_type = str(value.dtype) + else: + display_type = type(value).__name__ + + # Include shape information + if value.ndim == 1: + shape = value.shape + display_type = f"{display_type} ({shape[0]})" + else: + display_type = f"{display_type} {value.shape}" return display_type - def get_child_names(self, value) -> list: + def get_length(self, value: Any) -> int: + return value.shape[0] + + def get_column_names(self, value: Any) -> list: try: - return list(map(str, list(range(len(value))))) + dimensions = value.ndim + columns = range(dimensions) + return list(map(str, list(columns))) except Exception: return [] - def has_child(self, value, child_name) -> bool: - return child_name in self.get_child_names(value) + def has_child(self, value: Any, child_name: str) -> bool: + try: + index = int(child_name) + return index < self.get_length(value) + except Exception: + logging.warning(f"Unable to find Pandas Series child at '{child_name}'", exc_info=True) + return False - def get_child_info(self, value, child_name) -> Tuple[str, Any]: + def get_child(self, value: Any, child_name: str) -> Any: + child_value = None try: - child = value[int(child_name)] - child_display_type = type(child).__name__ - return (child_display_type, child) + index = int(child_name) + if value.ndim == 1: + dimension = value.tolist() + child_value = dimension[index] + else: + child_value = value[:, index].tolist() + + return child_value except Exception: logging.warning("Unable to get ndarray child: %s", child_name, exc_info=True) - return ("unknown", []) + return [] + + def summarize_children( + self, value: Any, summarizer: Callable[[str, Any], Optional[dict]] + ) -> list: + # Treat collection items as children, with the index as the name + children = [] + try: + items = value.tolist() + for i, item in enumerate(items): + if len(children) >= MAX_ITEMS: + break + + summary = summarizer(str(i), item) + if summary is not None: + children.append(summary) + except Exception: + logging.warning("Error summarizing Numpy ndarray children", exc_info=True) + + return children - def equals(self, value1, value2) -> bool: + def equals(self, value1: Any, value2: Any) -> bool: # Try to use numpy's array_equal try: import numpy as np @@ -423,7 +662,7 @@ def equals(self, value1, value2) -> bool: return False return value1.tobytes() == value2.tobytes() - def copy(self, value) -> Any: + def copy(self, value: Any) -> Any: return value.copy() @@ -468,7 +707,7 @@ def get_inspector(value) -> PositronInspector: return inspector -def _get_kind(value) -> Optional[str]: +def _get_kind(value) -> str: if isinstance(value, str): return "string" elif isinstance(value, bool): diff --git a/extensions/positron-python/pythonFiles/positron/positron_ipkernel.py b/extensions/positron-python/pythonFiles/positron/positron_ipkernel.py index a3de1b5797f..93f9d6d50b2 100644 --- a/extensions/positron-python/pythonFiles/positron/positron_ipkernel.py +++ b/extensions/positron-python/pythonFiles/positron/positron_ipkernel.py @@ -56,6 +56,9 @@ def __init__(self, **kwargs): shell.events.register("post_execute", self.handle_post_execute) self.get_user_ns_hidden().update(POSITON_NS_HIDDEN) + # Set the traceback mode to minimal by default + shell.InteractiveTB.set_mode(mode="Minimal") + # Setup Positron's environment service self.env_service = EnvironmentService(self) self.comm_manager.register_target(POSITRON_ENVIRONMENT_COMM, self.env_service.on_comm_open) @@ -242,29 +245,13 @@ def find_var(self, path: Iterable) -> Tuple[bool, Any]: is_known = hasattr(context, name) if is_known: value = getattr(context, name, None) - - elif is_inspectable(context): + else: + # Check for membership via inspector inspector = get_inspector(context) - is_known = inspector.has_child(context, name) - if is_known: - display_type, value = inspector.get_child_info(context, name) - - # Check for membership by dict key - elif isinstance(context, Mapping): - value = context.get(name, __POSITRON_DEFAULT__) - if value is __POSITRON_DEFAULT__: - is_known = False - else: - is_known = True - - # Check for membership by collection index - elif isinstance(context, (list, tuple, range)): - try: - value = context[int(name)] - is_known = True - except Exception: - logging.warning(f"Unable to find variable at '{path}'", exc_info=True) - is_known = False + if inspector is not None: + is_known = inspector.has_child(context, name) + if is_known: + value = inspector.get_child(context, name) # Subsequent segment starts from the value context = value @@ -289,6 +276,7 @@ def view_var(self, path: Sequence) -> None: if is_known: inspector = get_inspector(value) if inspector is not None: + # Use the leaf segment as the title title = path[-1:][0] dataset = inspector.to_dataset(value, title) if dataset is not None: