feat: introduce better_inspect module for enhanced dir() and help() functionality for marimo-pair#9223
feat: introduce better_inspect module for enhanced dir() and help() functionality for marimo-pair#9223
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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_inspectwithbetter_dir(),better_help(), and a@helpabledecorator (plus enum metaclass support). - Apply
@helpableto code-mode types (including convertingCellStatusTypefrom aLiteralto astr-backedEnum). - 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. |
| @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. | ||
| """ |
There was a problem hiding this comment.
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.
| for name in _public_attrs(obj): | ||
| val: Any = getattr(obj, name, None) | ||
|
|
There was a problem hiding this comment.
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.
| for name in _public_attrs(obj): | ||
| val: Any = getattr(obj, name, None) | ||
|
|
There was a problem hiding this comment.
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.
| lines: list[str] = [f"# {cls.__name__}"] | ||
|
|
||
| if cls.__doc__: | ||
| lines.append(cls.__doc__.strip().split("\n")[0]) | ||
|
|
There was a problem hiding this comment.
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.
| 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]) |
| if cls.__doc__: | ||
| lines.append(cls.__doc__.strip().split("\n")[0]) |
There was a problem hiding this comment.
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.
| 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()) |
…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
manzt
left a comment
There was a problem hiding this comment.
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.
|
@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()) |
# Conflicts: # marimo/_code_mode/_context.py
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.2-dev49 |
_better_inspect.pytomarimo/_code_mode/withbetter_dir(),better_help(), and@helpabledecoratorNotebookCell,CellError,CellRuntimeState,CellsView,AsyncCodeModeContextwith@helpableCellStatusTypefromLiteral[...]tostr, Enumwith cleandir()/help()supportThese are the types exposed to agents working inside code-mode notebooks.
dir()andhelp()on them now return compact, AI-friendly output instead of Python's default noise.dir(AsyncCodeModeContext)— before / afterBefore (86 entries, truncated)
After (13 entries):
dir(NotebookCell)— before / afterBefore
After:
dir(CellStatusType)— before / afterBefore (
Literal[...]):After (
str, Enum):help(AsyncCodeModeContext)—__doc__output