Skip to content

feat: introduce better_inspect module for enhanced dir() and help() functionality for marimo-pair#9223

Merged
mscolnick merged 4 commits intomainfrom
ms/better-inspect
Apr 17, 2026
Merged

feat: introduce better_inspect module for enhanced dir() and help() functionality for marimo-pair#9223
mscolnick merged 4 commits intomainfrom
ms/better-inspect

Conversation

@mscolnick
Copy link
Copy Markdown
Contributor

@mscolnick mscolnick commented Apr 16, 2026

  • Add _better_inspect.py to marimo/_code_mode/ with better_dir(), better_help(), and @helpable decorator
  • Annotate NotebookCell, CellError, CellRuntimeState, CellsView, AsyncCodeModeContext with @helpable
  • Convert CellStatusType from Literal[...] to str, Enum with clean dir()/help() support

These are the types exposed to agents working inside code-mode notebooks. dir() and help() on them now return compact, AI-friendly output instead of Python's default noise.

dir(AsyncCodeModeContext) — before / after

Before (86 entries, truncated)
['__aenter__', '__aexit__', '__class__', '__delattr__', '__dict__', '__dir__',
 '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
 '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__',
 '__new__', '__reduce__', '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__', '_apply_ops', '_cell_label', '_dry_run_compile',
 '_entered', '_format_plan', '_id_generator', '_kernel', '_ops',
 '_packages_to_install', '_pending_adds', '_print_summary', '_require_entered',
 '_resolve_new_cell', '_resolve_target', '_skip_validation', '_ui_updates',
 'cells', 'create_cell', 'delete_cell', 'edit_cell', 'enqueue_command',
 'execute_command', 'globals', 'graph', 'install_packages', 'move_cell',
 'notify', 'run_cell', 'set_ui_value']

After (13 entries):

['cells: _CellsView',
 "create_cell(self, code: 'str', ...) -> 'CellId_t'",
 "delete_cell(self, target: 'str') -> 'None'",
 "edit_cell(self, target: 'str', ...) -> 'None'",
 "enqueue_command(self, command: 'CommandMessage') -> 'None'",
 "execute_command(self, command: 'CommandMessage') -> 'None'",
 'globals: dict[str, Any]',
 'graph: DirectedGraph',
 "install_packages(self, *packages: ...) -> 'None'",
 "move_cell(self, target: 'str', ...) -> 'None'",
 "notify(self, notification: 'Notification') -> 'None'",
 "run_cell(self, target: 'str') -> 'None'",
 "set_ui_value(self, element: 'Any', value: 'Any') -> 'None'"]

dir(NotebookCell) — before / after

Before
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', ...,
 '_cell', '_graph_errors', '_impl', '_is_stale',
 'code', 'config', 'errors', 'id', 'name', 'status']

After:

['code: str',
 'config: CellConfig',
 'errors: list[CellError]',
 'id: CellId_t',
 'name: str',
 'status: CellStatusType | None']

dir(CellStatusType) — before / after

Before (Literal[...]):

>>> dir(CellStatusType)
['__args__', '__call__', '__class__', '__origin__', '__parameters__', ...]
# Literal type alias — no useful introspection at all

After (str, Enum):

["cancelled = 'cancelled'",
 "disabled = 'disabled'",
 "exception = 'exception'",
 "idle = 'idle'",
 "interrupted = 'interrupted'",
 "marimo_error = 'marimo-error'",
 "queued = 'queued'",
 "running = 'running'",
 "stale = 'stale'"]

help(AsyncCodeModeContext)__doc__ output

# AsyncCodeModeContext
Async programmatic control of a running marimo notebook.

Attributes:
  cells: _CellsView
  globals: dict[str, Any]
  graph: DirectedGraph
  kernel: Kernel

Methods:
  create_cell(code, ...) -> CellId_t  -- Queue a new cell.
  delete_cell(target) -> None  -- Queue a cell for deletion.
  edit_cell(target, ...) -> None  -- Queue an update to an existing cell.
  run_cell(target) -> None  -- Queue a cell for execution.
  ...

…unctionality for marimo-pair

This commit adds a new module, better_inspect, which provides improved versions of the built-in dir() and help() functions. The new functions, better_dir() and better_help(), filter out private and dunder attributes, append type annotations and method signatures, and return outputs that are more user-friendly and suitable for AI tools. Additionally, a @helpable decorator is introduced to enhance class documentation and integration with help().

Also includes tests for the new functionality and a smoke test to validate the integration within the marimo application.
Copilot AI review requested due to automatic review settings April 16, 2026 15:28
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 17, 2026 5:03pm

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces an internal “better inspect” utility under marimo._code_mode to produce cleaner, AI-friendly outputs for dir() and help(), and applies it to key code-mode classes so interactive inspection is less noisy.

Changes:

  • Add marimo._code_mode._better_inspect with better_dir(), better_help(), and a @helpable decorator (plus enum metaclass support).
  • Apply @helpable to code-mode types (including converting CellStatusType from a Literal to a str-backed Enum).
  • Add unit tests for the new inspect behavior and a smoke test notebook exercising help()/dir() in the app.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
marimo/_code_mode/_better_inspect.py New implementation of better_dir/better_help and @helpable wiring for cleaner introspection.
marimo/_code_mode/_context.py Adopts @helpable on core code-mode classes and introduces CellStatusType as an enum for nicer inspection.
tests/_code_mode/test_better_inspect.py Adds focused tests for filtering/formatting behavior and decorator integration.
marimo/_smoke_tests/code_mode/help_and_dir.py Adds a smoke test notebook that runs help()/dir() on code-mode APIs.

Comment on lines +96 to +102
@helpable
class CellStatusType(str, Enum, metaclass=_HelpableEnumMeta):
"""Synthesized cell execution status.

Returned by ``NotebookCell.status``. Compares equal to plain
strings, so ``cell.status == "idle"`` works as expected.
"""
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

CellStatusType is now an Enum, but NotebookCell.status (and its __repr__ snapshots in existing tests) still operate on plain string values (e.g. "queued", "stale"). With the current change, the CellStatusType | None annotation becomes inaccurate, and callers can’t rely on isinstance(status, CellStatusType). Either (a) update status to return enum members (and adjust repr expectations accordingly, e.g. by customizing enum __repr__ if you want to keep 'idle'), or (b) keep status returning str | None and treat CellStatusType as a documentation/introspection enum rather than the runtime type.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +143
for name in _public_attrs(obj):
val: Any = getattr(obj, name, None)

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

better_dir uses getattr(obj, name, None) for every public attribute. This will execute property getters / custom descriptors and can raise exceptions or cause side effects just from calling dir() (unexpected) and can also be expensive. Consider using inspect.getattr_static() (or similar) to avoid invoking descriptors, and handle failures per-attribute so better_dir never raises due to an attribute access.

Copilot uses AI. Check for mistakes.
Comment on lines +210 to +212
for name in _public_attrs(obj):
val: Any = getattr(obj, name, None)

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

better_help also uses getattr(obj, name, None) while iterating attributes, which can execute properties / descriptors and raise or perform work simply by asking for help. help()/introspection utilities should be side-effect free; use inspect.getattr_static() (and avoid calling getters) and make the output resilient to attribute-access errors.

Copilot uses AI. Check for mistakes.
Comment thread marimo/_code_mode/_better_inspect.py Outdated
Comment on lines +200 to +204
lines: list[str] = [f"# {cls.__name__}"]

if cls.__doc__:
lines.append(cls.__doc__.strip().split("\n")[0])

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

better_help derives the heading from cls = type(obj) for non-types. For modules/functions this will render # module / # function rather than the actual object name, despite the docstring claiming it supports modules. Consider using getattr(obj, "__name__", cls.__name__) (and/or module __spec__) so the header reflects the object being documented.

Suggested change
lines: list[str] = [f"# {cls.__name__}"]
if cls.__doc__:
lines.append(cls.__doc__.strip().split("\n")[0])
obj_name = getattr(obj, "__name__", None)
if obj_name is None:
obj_spec = getattr(obj, "__spec__", None)
obj_name = getattr(obj_spec, "name", None)
title = obj_name or cls.__name__
doc_source: Any
if isinstance(obj, type):
doc_source = obj
elif obj_name is not None:
doc_source = obj
else:
doc_source = cls
lines: list[str] = [f"# {title}"]
if doc_source.__doc__:
lines.append(doc_source.__doc__.strip().split("\n")[0])

Copilot uses AI. Check for mistakes.
Comment thread marimo/_code_mode/_better_inspect.py Outdated
Comment on lines +202 to +203
if cls.__doc__:
lines.append(cls.__doc__.strip().split("\n")[0])
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

When better_help is called on a @helpable class/instance, cls.__doc__ has already been rewritten to the generated help (starting with # ClassName), so this currently duplicates the heading and replaces the original one-line description. Consider preferring _original_doc__ when present, or stripping the generated header from cls.__doc__ before appending the description line.

Suggested change
if cls.__doc__:
lines.append(cls.__doc__.strip().split("\n")[0])
raw_doc = getattr(cls, "_original_doc__", None) or cls.__doc__
if raw_doc:
doc_lines = raw_doc.strip().split("\n")
if doc_lines and doc_lines[0].strip() == f"# {cls.__name__}":
doc_lines = [line for line in doc_lines[1:] if line.strip()]
if doc_lines:
lines.append(doc_lines[0].strip())

Copilot uses AI. Check for mistakes.
Comment thread marimo/_code_mode/_better_inspect.py
Comment thread marimo/_code_mode/_better_inspect.py Outdated
Comment thread marimo/_code_mode/_better_inspect.py
…nsistency

- Use inspect.getattr_static so better_dir/better_help never invoke
  property getters during introspection
- Unwrap classmethod/staticmethod so they appear under Methods with
  correct signatures
- Prefer _original_doc__ in better_help to avoid duplicating the
  generated heading on @helpable classes
- Use obj.__name__ when available so modules/functions get proper titles
- Return CellStatusType enum members from NotebookCell.status; add
  __str__/__repr__ so string comparisons and reprs are unchanged
- Document the zero-arg super() caveat in @helpable
Copy link
Copy Markdown
Collaborator

@manzt manzt left a comment

Choose a reason for hiding this comment

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

Nice. My only hesitation is whether the model would ever want to use dir() or help() normally... probably not.

We could always tell the the model to use cm.help() or cm.dir() for things in code mode.

@mscolnick
Copy link
Copy Markdown
Contributor Author

@manzt i think we want to encourage them to use dir() and help() normally. we are overriding when it's safe, instead of overriding the world (if we said to use cm.help())

@mscolnick mscolnick added the enhancement New feature or request label Apr 16, 2026
@mscolnick mscolnick requested a review from manzt April 16, 2026 22:16
# Conflicts:
#	marimo/_code_mode/_context.py
@mscolnick mscolnick merged commit cab71a2 into main Apr 17, 2026
42 of 43 checks passed
@mscolnick mscolnick deleted the ms/better-inspect branch April 17, 2026 18:54
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.2-dev49

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants