Skip to content

Commit

Permalink
Merged PR posit-dev/positron-python#86: Add some additional documenta…
Browse files Browse the repository at this point in the history
…tion to positron python

Merge pull request #86 from posit-dev/positron-docs

Add some additional documentation to positron python
--------------------
Commit message for posit-dev/positron-python@4b19d94:

Add some additional documentation to positron python


Authored-by: Pete Farland <pete.farland@posit.co>
Signed-off-by: Pete Farland <pete.farland@posit.co>
  • Loading branch information
petetronic committed May 4, 2023
1 parent 8531217 commit fcb9d9d
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 52 deletions.
96 changes: 75 additions & 21 deletions extensions/positron-python/pythonFiles/positron/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,15 @@ def on_comm_open(self, comm, open_msg) -> None:

def receive_message(self, msg) -> None:
"""
Handle messages sent by the client to the positron.environment comm.
Handle messages received from the client via the positron.environment comm.
Message Types:
"inspect" - Inspect the user variable at the requested path
"refresh" - Refresh the list of user variables in the environment
"view" - Format the variable at the requested path for the data viewer
"clipboard_format" - Format the variable at the requested path for the client clipboard
"clear" - Clear all user variables in the environment
"delete" - Delete user variables in the environment by name
"""
data = msg["content"]["data"]

Expand Down Expand Up @@ -214,6 +222,10 @@ def receive_message(self, msg) -> None:
self._send_error(f"Unknown message type '{msgType}'")

def send_update(self, assigned: dict, removed: set) -> None:
"""
Sends the list of variables that have changed in the current user session through the
environment comm to the client.
"""
# Ensure the number of changes does not exceed our maximum items
if len(assigned) < MAX_ITEMS and len(removed) < MAX_ITEMS:
self._send_update(assigned, removed)
Expand Down Expand Up @@ -264,7 +276,7 @@ def _send_message(self, msg: EnvironmentMessage) -> None:

self.env_comm.send(msg)

def _send_error(self, message: str) -> None:
def _send_error(self, error_message: str) -> None:
"""
Send an error message through the envirvonment comm to the client.
Expand All @@ -277,7 +289,7 @@ def _send_error(self, message: str) -> None:
...
}
"""
msg = EnvironmentMessageError(message)
msg = EnvironmentMessageError(error_message)
self._send_message(msg)

def _send_update(self, assigned: Mapping, removed: Iterable) -> None:
Expand Down Expand Up @@ -312,12 +324,24 @@ def _send_update(self, assigned: Mapping, removed: Iterable) -> None:
def _delete_all_vars(self, parent) -> None:
"""
Deletes all of the variables in the current user session.
Args:
parent:
A dict providing the parent context for the response,
e.g. the client message requesting the clear operation
"""
self.kernel.delete_all_vars(parent)

def _delete_vars(self, names: Iterable, parent) -> None:
"""
Deletes the requested variables by name from the current user session.
Args:
names:
A list of variable names to delete
parent:
A dict providing the parent context for the response,
e.g. the client message requesting the delete operation
"""
if names is None:
return
Expand All @@ -328,12 +352,15 @@ def _delete_vars(self, names: Iterable, parent) -> None:
def _inspect_var(self, path: Sequence) -> None:
"""
Describes the variable at the requested path in the current user session.
Args:
path:
A list of names describing the path to the variable.
"""
if path is None:
return

is_known, value = self.kernel.find_var(path)

if is_known:
self._send_details(path, value)
else:
Expand All @@ -357,8 +384,14 @@ def _send_formatted_var(
Formats the variable at the requested path in the current user session
using the requested clipboard format and sends the result through the
environment comm to the client.
"""
Args:
path:
A list of names describing the path to the variable.
clipboard_format:
The format to use for the clipboard copy, described as a mime type.
Defaults to "text/plain".
"""
if path is None:
return

Expand All @@ -372,7 +405,7 @@ def _send_formatted_var(
message = f"Cannot find variable at '{path}' to format"
self._send_error(message)

def _send_details(self, path: Sequence, context: Any = None):
def _send_details(self, path: Sequence, value: Any = None):
"""
Sends a detailed list of children of the value (or just the value
itself, if is a leaf node on the path) as a message through the
Expand All @@ -397,15 +430,21 @@ def _send_details(self, path: Sequence, context: Any = None):
}
...
}
Args:
path:
A list of names describing the path to the variable.
value:
The variable's value to summarize.
"""

children = []
inspector = get_inspector(context)
if inspector is not None and inspector.has_children(context):
children = inspector.summarize_children(context, self._summarize_variable)
inspector = get_inspector(value)
if inspector.has_children(value):
children = inspector.summarize_children(value, self._summarize_variable)
else:
# Otherwise, treat as a simple value at given path
summary = self._summarize_variable("", context)
summary = self._summarize_variable("", value)
if summary is not None:
children.append(summary)
# TODO: Handle scalar objects with a specific message type
Expand All @@ -414,11 +453,19 @@ def _send_details(self, path: Sequence, context: Any = None):
self._send_message(msg)

def _summarize_variables(self, variables: Mapping, max_items: int = MAX_ITEMS) -> list:
"""
Summarizes the given variables into a list of EnvironmentVariable objects.
Args:
variables:
A mapping of variable names to values.
max_items:
The maximum number of items to summarize.
"""
summaries = []

for key, value in variables.items():
# Ensure the number of items summarized is within our
# max limit
# Ensure the number of items summarized is within our max limit
if len(summaries) >= max_items:
break

Expand All @@ -428,22 +475,28 @@ def _summarize_variables(self, variables: Mapping, max_items: int = MAX_ITEMS) -

return summaries

def _summarize_variable(self, key, value) -> Optional[EnvironmentVariable]:
def _summarize_variable(self, key: Any, value: Any) -> Optional[EnvironmentVariable]:
"""
Summarizes the given variable into an EnvironmentVariable object.
Returns:
An EnvironmentVariable summary, or None if the variable should be skipped.
"""
# Hide module types for now
if isinstance(value, types.ModuleType):
return None

display_name = str(key)

try:
# Use an inspector to summarize the value
ins = get_inspector(value)

display_name = ins.get_display_name(key)
kind_str = ins.get_kind(value)
kind = getattr(EnvironmentVariableKind, kind_str.upper())
display_value, is_truncated = ins.get_display_value(value)
display_type = ins.get_display_type(value)
type_info = ins.get_type_info(value)
access_key = ins.get_access_key(key)
length = ins.get_length(value)
size = ins.get_size(value)
has_children = ins.has_children(value)
Expand All @@ -455,7 +508,7 @@ def _summarize_variable(self, key, value) -> Optional[EnvironmentVariable]:
display_type=display_type,
kind=kind,
type_info=type_info,
access_key=display_name,
access_key=access_key,
length=length,
size=size,
has_children=has_children,
Expand All @@ -466,17 +519,18 @@ def _summarize_variable(self, key, value) -> Optional[EnvironmentVariable]:
except Exception as err:
logging.warning(err, exc_info=True)
return EnvironmentVariable(
display_name=display_name,
display_name=str(key),
display_value=get_qualname(value),
kind=EnvironmentVariableKind.OTHER,
)

def _format_value(self, value, clipboard_format: ClipboardFormat) -> str:
def _format_value(self, value: Any, clipboard_format: ClipboardFormat) -> str:
"""
Formats the given value using the requested clipboard format.
"""
inspector = get_inspector(value)

if clipboard_format == ClipboardFormat.HTML:
return inspector.to_html(value)
elif clipboard_format == ClipboardFormat.PLAIN:
return inspector.to_tsv(value)
else:
return str(value)
return inspector.to_plaintext(value)
32 changes: 24 additions & 8 deletions extensions/positron-python/pythonFiles/positron/inspectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@
# conditional property lookup
__POSITRON_DEFAULT__ = object()

# Base inspector for any type
#
# Base inspector
#


class PositronInspector:
"""
Base inspector for any type
"""

def get_display_name(self, key: Any) -> str:
return str(key)

def get_display_value(
self,
value: Any,
Expand Down Expand Up @@ -75,6 +80,9 @@ def get_kind(self, value: Any) -> str:
def get_type_info(self, value: Any) -> str:
return get_qualname(value)

def get_access_key(self, name: Any) -> str:
return self.get_display_name(name)

def get_length(self, value: Any) -> int:
return get_value_length(value)

Expand Down Expand Up @@ -113,11 +121,13 @@ def to_dataset(self, value: Any, title: str) -> Optional[DataSet]:
def to_html(self, value: Any) -> str:
return repr(value)

def to_tsv(self, value: Any) -> str:
def to_plaintext(self, value: Any) -> str:
return repr(value)


#
# Inspectors by kind
#


class BooleanInspector(PositronInspector):
Expand Down Expand Up @@ -318,7 +328,9 @@ def is_snapshottable(self, value: Any) -> bool:
return True


#
# Custom inspectors for specific types
#


class PandasDataFrameInspector(TableInspector):
Expand Down Expand Up @@ -406,7 +418,7 @@ def to_dataset(self, value: Any, title: str) -> Optional[DataSet]:
def to_html(self, value: Any) -> str:
return value.to_html()

def to_tsv(self, value: Any) -> str:
def to_plaintext(self, value: Any) -> str:
return value.to_csv(path_or_buf=None, sep="\t")


Expand Down Expand Up @@ -481,9 +493,9 @@ def copy(self, value: Any) -> Any:

def to_html(self, value: Any) -> str:
# TODO: Support HTML
return self.to_tsv(value)
return self.to_plaintext(value)

def to_tsv(self, value: Any) -> str:
def to_plaintext(self, value: Any) -> str:
return value.to_csv(path_or_buf=None, sep="\t")


Expand Down Expand Up @@ -570,7 +582,7 @@ def to_dataset(self, value: Any, title: str) -> Optional[DataSet]:
def to_html(self, value: Any) -> str:
return value._repr_html_()

def to_tsv(self, value: Any) -> str:
def to_plaintext(self, value: Any) -> str:
return value.write_csv(file=None, separator="\t")


Expand Down Expand Up @@ -695,8 +707,12 @@ def copy(self, value: Any) -> Any:
"table": TableInspector(),
}

#
# Helper functions
#


def get_inspector(value) -> PositronInspector:
def get_inspector(value: Any) -> PositronInspector:
# Look for a specific inspector by qualified classname
qualname = get_qualname(value)
inspector = INSPECTORS.get(qualname, None)
Expand All @@ -713,7 +729,7 @@ def get_inspector(value) -> PositronInspector:
return inspector


def _get_kind(value) -> str:
def _get_kind(value: Any) -> str:
if isinstance(value, str):
return "string"
elif isinstance(value, bool):
Expand Down
Loading

0 comments on commit fcb9d9d

Please sign in to comment.