expand
Traverse one outbound edge from a resolved handle to retrieve adjacent symbols as stubs. Supports edges like members, callees, imports, subclasses, and enclosing_scope.
Instructions
Python: Expand one outbound edge from a canonical handle (single hop).
The traversal primitive that walks ONE edge from a source handle and returns adjacent symbols as lightweight Stubs. Use resolve()/inspect() first to obtain a canonical handle, then call expand() to traverse.
Supported edges (the complete static/outbound set):
members— class/module → direct members (attributes, methods, nested classes).stubs: []means the class/module was found but has no members; that is NOT the same as unsupported. Static-surface ceiling: members are read from source; runtime-injected members (metaclass /setattr/__getattr__/type()/__init_subclass__) are NOT captured — e.g. a DjangoModelshows none of its metaclass-injected_meta/objects/DoesNotExist.callees— function/method → forward static call targets. Includes project symbols and stdlib/external symbols reachable via Jedi's goto. Dynamic calls (un-inferable parameters,getattr, lambdas, etc.) are counted inunresolved_call_sitesrather than invented.imported_by— module → the project modules that import it (module Stubs), computed by static AST import-graph reversal (no reverse symbol search). Covers importers anywhere in the project including tests and standalone scripts. Non-module handles return the unsupported branch withreason: "not_yet_implemented"(symbol-levelimported_byis not yet implemented). Ceiling: runtime-dynamic imports (importlib/__import__with computed targets) are not detected.subclasses— class → the project classes that directly subclass it (class Stubs), computed by an AST class-graph walk + forwardgoto(no reverse symbol search). Returns the DIRECT (depth-1) subclasses only (#422) — one hop, symmetric withsuperclasses; the full transitive closure is served bytrace(follow=["subclasses"], max_depth=k, max_nodes=N), which carries the cap +truncatedcontract. A class result includes a statictransitive_hintfield pointing to that trace route.subclassesis an expand-only edge:inspectdoes NOT measure it (dropped in #392); a cheap direct count is gated on the Pyright reference backend / class-graph cache (#333/#397), because even the direct count is a reverse query needing the same project-wide scan ascallers/references.stubs: []means the class has no project subclasses (measured-none). A non-class handle also returns the supported branch withstubs: [](and notransitive_hint) — only a class CAN be subclassed, so[]is true by definition, not an absence-vs-zero lie. Static-surface ceiling: the result is complete only over literalclass B(A):subclassing; dynamically-created subclasses (type('B', (A,), {}), factory-built classes,__init_subclass__registration) are NOT captured.superclasses— class → its base classes (class Stubs), resolved by Jedi from the class definition (no reverse search). A non-class handle returnsstubs: []([]true by definition, as withsubclasses).imports— module → the symbols/modules it imports (Stubs), computed by static AST + forwardgoto.stubs: []is measured-none. A non-module handle returns the unsupported branch withreason: "not_yet_implemented"(mirrorsimported_by).enclosing_scope— symbol → its immediate lexical enclosing scope (the inverse ofmembers), resolved by Jediparent(): a method → its class, a nested def/class → its enclosing def/class, a top-level def/class/variable → its module. At most ONE Stub. A module returnsstubs: [](a module has no enclosing lexical scope — packages are not lexical scopes);[]is therefore measured-empty, never unsupported.
Unsupported edges return the unsupported branch (never raise):
Inbound/reference edges (
callers,references,overrides, …) require the Pyright reference backend (#333) and returnunsupported: true, reason: "deferred_reference_backend".Wrong-kind handles (e.g.
imported_byon a non-module) return the unsupported branch withreason: "not_yet_implemented".Unrecognised edge names return
reason: "unknown_edge".
Response shape — discriminated union:
Supported branch ("unsupported" key absent):
::
{ "source": str, # canonical source handle
"edge": str,
"stubs": [Stub, ...], # [] == measured-empty (NOT unsupported)
"unresolved_call_sites": int # callees ONLY; absent for members }Unsupported branch ("stubs" key absent):
::
{ "source": str,
"edge": str,
"unsupported": True,
"reason": str, # deferred_reference_backend |
# not_yet_implemented | unknown_edge
"detail": str } # human-readable explanationEach Stub carries: handle, kind, scope, line_start,
line_end, and signature when Jedi yields one (always for
class/function/method; also any name whose inferred type is callable).
Relationship to deprecated tools: members supersedes the deprecated
find_subclasses/find_symbol pattern for enumerating class members.
callees supersedes manual get_call_hierarchy usage for forward edges.
Both deprecated tools remain registered until Phase B migration.
Args:
handle: Canonical Python dotted-name string (from resolve/inspect).
edge: The outbound edge to expand (e.g. "members", "callees").
project_path: Project root path (default: current directory).
Returns: ExpandResult dict — supported branch or unsupported branch (see above). Never raises; unresolvable source handles yield graceful supported-empty results consistent with inspect()'s minimal-node contract.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| handle | Yes | ||
| edge | Yes | ||
| project_path | No | . |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||