Extracting and inspecting stacks¶
Extracting a stack¶
The main entry point for stackscope is the stackscope.extract() function.
It comes in several variants:
extract()accepts a “stack item”, which is anything stackscope knows how to turn into a series of frame objects. This might be aStackSlice, coroutine object, generator iterator, thread, greenlet,trio.lowlevel.Task, or anything else for which a library author (maybe you) have added support through theunwrap_stackitem()customization hook.extract_since()andextract_until()obtain a stack for the callees or callers (respectively) of a currently-executing frame. Useextract_since(None)to get the full stack of the thread that made theextract_since()call, likeinspect.stack(). These are aliases for invokingextract()with aStackSlice.extract_outermost()returns the outermostFramethatextract()would return, without computing the whole stack.
- stackscope.extract(stackitem: StackItem, *, with_contexts: bool = True, recurse_child_tasks: bool = False) Stack¶
Extract a
Stackfrom stackitem.stackitem may be anything that has a stack associated with it. If you want to dump the caller’s stack or the stack starting or ending with some frame, then either pass a
StackSliceor useextract_since()orextract_until(), which are shortcuts for passing aStackSlicetoextract(). Besides that, stackscope also ships with support for threads, greenlets, generator iterators (sync and async), coroutine objects, and a few more obscure things that might be encountered while traversing those; and libraries may add support for more using theunwrap_stackitemhook.If with_contexts is True (the default), then each returned
Framewill have acontextsattribute specifying the context managers that are currently active in that frame. If you don’t care about this information, then specifyingwith_contexts=Falsewill substantially simplify the stack extraction process.Some context managers might logically contain other tasks that each have their own
Stack:trio.Nursery,asyncio.TaskGroup, etc. These will be listed asContext.childrenin the returnedFrame.contextsfor these context managers. If recurse_child_tasks is False (the default), then these “child tasks” will be rendered as stubStackobjects with only aroot(the child task object) but noframes. If recurse_child_tasks is True, then the child stacks will be fully populated, including grandchildren and so on.extract()tries not to throw exceptions; any exception should be reported as a bug. Errors encountered during stack extraction are reported in theerrorattribute of the returned object. If multiple errors are encountered, they will be wrapped in anExceptionGroup.
- stackscope.extract_since(outer_frame: types.FrameType | None, *, with_contexts: bool = True, recurse_child_tasks: bool = False) Stack¶
Return a
Stackreflecting the currently-executing frames that were directly or indirectly called by outer_frame, including outer_frame itself. Equivalent toextract(StackSlice(outer=outer_frame))with more type checking.If outer_frame is a frame on the current thread’s stack, the result will start with outer_frame and end with the immediate caller of
extract_since().If outer_frame is a frame on some other thread’s stack, and it remains there throughout the traceback extraction process, the resulting stack will start with outer_frame and end with some frame that was recently the innermost frame on that thread’s stack.
Note
If other_frame is not continuously on the same other thread’s stack during the extraction process, you’re likely to get a one-frame stack, maybe with an
error. It’s not possible to prevent thread switching from within Python code, so we can’t do better than this without a C extension.If outer_frame is None, the result contains all frames on the current thread’s stack, starting with the outermost and ending with the immediate caller of
extract_since().In any other case – if outer_frame belongs to a suspended coroutine, generator, greenlet, or if it starts or stops running on another thread while
extract_since()is executing – you will get aStackcontaining information only on outer_frame itself; depending on the situation, itserrormember might describe the reason more information can’t be provided.
- stackscope.extract_until(inner_frame: types.FrameType, *, limit: int | types.FrameType | None = None, with_contexts: bool = True, recurse_child_tasks: bool = False) Stack¶
Return a
Stackreflecting the currently executing frames that are direct or indirect callers of inner_frame, including inner_frame itself.If inner_frame belongs to a suspended coroutine or generator, or if it otherwise is not linked to other frames via its
f_backattribute, then the returned traceback will contain only inner_frame and not any of its callers.If a limit is specified, only some of the callers of inner_frame will be returned. If the limit is a frame, then it must be an indirect caller of inner_frame and it will be the first frame in the result; any of its callers will be excluded. Otherwise, the limit must be a positive integer, and the traceback will start with the limit’th parent of inner_frame.
Equivalent to
extract(StackSlice(outer=outer_frame, limit=limit))orextract(StackSlice(outer=outer_frame, inner=limit))depending on the type of limit, except thatextract_until()does more checking of its inputs (an exception will be raised if limit has an invalid type or is a frame that isn’t an indirect caller of inner_frame).
- stackscope.extract_outermost(stackitem: StackItem, *, with_contexts: bool = True, recurse_child_tasks: bool = False) Frame¶
Extract the outermost
Framefrom stackitem.extract_outermost()produces the same result as callingextract()and returning the firstFrameof the returned stack, but might be faster since it can stop once it’s extracted one frame. If the result has noFrames, an exception will be thrown.
- class stackscope.StackSlice(outer: types.FrameType | None = None, inner: types.FrameType | None = None, limit: int | None = None)¶
Identifies a contiguous series of frames that we want to analyze.
StackSlicehas no logic on its own; its only use is as something to pass toextract()or return from anunwrap_stackitem()hook.This can be used in three different ways:
If
inneris not None, then theStackSlicelogically contains currently-executing frames that are direct or indirect callers ofinner, ending withinneritself. Iteration begins withinnerand proceeds outward viaframe.f_backlinks until the frameouteris reached orlimitframes have been extracted. If neither of those is specified, then the stack slice starts with the outermost frame of the thread on whichinneris running.If
innerbelongs to a suspended coroutine or generator, or if it otherwise is not linked to other frames via itsf_backattribute, then the returned traceback will contain onlyinnerand not any of its callers.If
inneris None butouteris not, theStackSlicecontainsouterfollowed by its currently-executing direct and indirect callees, up tolimitframes total.If
outeris executing on the current thread, then theStackSliceends with the frame that calledstackscope.extract()(unless it is cut off before that by reaching itslimit). If it is executing on some other thread, and remains so throughout the stack extraction process, then theStackSliceends with the innermost frame on that thread. In any other case – ifouterbelongs to a suspended coroutine, generator, greenlet, or if it starts or stops running on another thread whilestackscope.extract()is executing – the returned stack will contain information only onouteritself.If
innerandouterare both None, theStackSlicecontains the entirety of the current thread’s stack, ending with the frame that made the call tostackscope.extract().
- outer: types.FrameType | None = None¶
The outermost frame to extract. If unspecified, start from
inner(or the caller ofstackscope.extract()ifinneris None) and iterate outward until top-of-stack is reached orlimitframes have been extracted.
- class stackscope.StackItem¶
Placeholder used in type hints to mean “anything you can pass to
extract().”
- exception stackscope.InspectionWarning¶
Warning raised if something goes awry during frame inspection.
Working with stacks¶
This section documents the Stack object that extract()
returns, as well as the Frame and Context objects that it refers
to. All of these are dataclasses. Their primary purpose is to
organize the data returned by extract().
- class stackscope.Stack(root: object, frames: Sequence[Frame], leaf: object = None, error: Exception | None = None)¶
Representation of a generalized call stack.
In addition to the attributes described below, you can treat a
Stacklike an iterable over itsframes, and itslen()is the number of frames it contains.- frames: Sequence[Frame]¶
The series of frames (individual calls) in the call stack, from outermost/oldest to innermost/newest.
- root: object¶
The object that was originally passed to
extract()to produce this stack trace, orNoneif the trace was created from aStackSlice(which doesn’t carry any information beyond the frames).
- leaf: object = None¶
An object that provides additional context on what this call stack is doing, after you peel away all the frames.
If this callstack comes from a generator that is yielding from an iterator which is not itself a generator, or comes from an async function that is awaiting an awaitable which is not itself a coroutine, then leaf will be that iterator or awaitable.
Library glue may provide additional semantics for leaf; for example, the call stack of an async task that is waiting on an event might set leaf to that event.
If there is no better option, leaf will be None.
- error: Exception | None = None¶
The error encountered walking the stack, if any. (
stackscopedoes its best to not actually raise exceptions out ofextract().)
- format(*, ascii_only: bool = False, show_contexts: bool = True, show_hidden_frames: bool = False) List[str]¶
Return a list of newline-terminated strings describing this object, which may be printed for human consumption.
str(obj)is equivalent to"".join(obj.format()).- Parameters:
ascii_only – Use only ASCII characters in the output. By default, Unicode line-drawing characters are used.
show_contexts – Include information about context managers in the output. This is the default; pass False for a shorter stack trace that only includes frames in the main series.
show_hidden_frames – Include frames in the output even if they are marked as hidden. By default, hidden frames will be suppressed. See
Frame.hidefor more details.
- format_flat(*, show_contexts: bool = False) List[str]¶
Return a list of newline-terminated strings providing a flattened representation of this stack.
This will be formatted similarly to a standard Python traceback, such as might be produced if an exception were raised at the point where the stack was extracted. Context manager information is not included by default, but can be requested using the show_contexts parameter.
- as_stdlib_summary(*, show_contexts: bool = False, show_hidden_frames: bool = False, capture_locals: bool = False) StackSummary¶
Return a representation of this stack as a standard
traceback.StackSummary. Unlike theStackobject, aStackSummarycan be pickled and will not keep frames alive, at the expense of some loss of information.If show_contexts is True, then additional frame summaries will be emitted describing the context managers active in each frame. See the documentation of
Frame.as_stdlib_summary_with_contexts()for details.By default, hidden frames (
Frame.hide) are not included in the output. You can use the show_hidden_frames parameter to override this.capture_locals is passed through to the
Frame.as_stdlib_summary()calls for each stack frame; see that method’s documentation for details on its semantics.
- class stackscope.Frame(pyframe: types.FrameType, lineno: int = -1, origin: StackItem | None = None, contexts: Sequence[Context] = (), hide: bool = False, hide_line: bool = False)¶
Representation of one call frame within a generalized call stack.
- pyframe: types.FrameType¶
The Python frame object that this frame describes.
- lineno: int = -1¶
A line number in
pyframe.This is the currently executing line number, or the line number at which it will resume execution if currently suspended by a
yieldstatement or greenlet switch, as captured when theFrameobject was constructed.
- origin: StackItem | None = None¶
The innermost weak-referenceable thing that we looked inside to find this frame, or None. Frames themselves are not weak-referenceable, but
extract_outermost(frame.origin).pyframewill recover the originalframe.pyframe. For example, when traversing an async call stack,originmight be a coroutine object or generator iterator.This is exposed for use in async debuggers, which might want a way to get ahold of a previously-reported frame if it’s still running, without keeping it pinned in memory if it’s finished.
- contexts: Sequence[Context] = ()¶
The series of contexts (
withorasync withblocks) that are active in this frame, from outermost to innermost. A context is considered “active” for this purpose from the point where its manager’s__enter__or__aenter__method returns until the point where its manager’s__exit__or__aexit__method returns.
- hide: bool = False¶
If true, this frame relates to library internals that are likely to be more distracting than they are useful to see in a traceback. Analogous to the
__tracebackhide__variable supported by pytest. Hidden frames are suppressed by default when printing stacks, but this can be controlled using the show_hidden_frames argument toformat().
- hide_line: bool = False¶
Limited version of
hidewhich by default suppresses display of the executing line, but not of the function information or context managers associated with the frame. As withhide, you can force the hidden information to be displayed by specifying the show_hidden_frames argument toformat().
- property filename: str¶
The filename of the Python file from which the code executing in this frame was imported.
- property clsname: str | None¶
The name of the class that contains the function executing in this frame, or None if we couldn’t determine one.
This is determined heuristically, based on the executing function having a first argument named
selforcls, so it can be fooled.
- property modname: str | None¶
The name of the module that contains the function executing in this frame, or None if we couldn’t determine one.
This is looked up using the
__name__attribute of the frame’s globals namespace. It can be fooled, but usually won’t be. Another option, which is possibly more reliable but definitely much slower, would be to iterate throughsys.moduleslooking for a module whose__file__matches this frame’sfilename.
- property linetext: str¶
The text of the line of source code that this stack entry describes. The result has leading and trailing whitespace stripped, and does not end in a newline.
- format(*, ascii_only: bool = False, show_contexts: bool = True, show_hidden_frames: bool = False) List[str]¶
Return a list of newline-terminated strings describing this object, which may be printed for human consumption.
str(obj)is equivalent to"".join(obj.format()).- Parameters:
ascii_only – Use only ASCII characters in the output. By default, Unicode line-drawing characters are used.
show_contexts – Include information about context managers in the output. This is the default; pass False for a shorter stack trace that only includes frames in the main series.
show_hidden_frames – Include frames in the output even if they are marked as hidden. By default, hidden frames will be suppressed. See
Frame.hidefor more details.
- as_stdlib_summary(*, capture_locals: bool = False) FrameSummary¶
Return a representation of this frame entry as a standard
traceback.FrameSummaryobject. Unlike theFrameobject, aFrameSummarycan be pickled and will not keep frames alive, at the expense of some loss of information.If capture_locals is True, then the returned
FrameSummarywill contain the stringified object representations of local variables in the frame, just like passingcapture_locals=Truetotraceback.StackSummary.extract().
- for ... in as_stdlib_summary_with_contexts(*, show_hidden_frames: bool = False, capture_locals: bool = False) Iterator[FrameSummary]¶
Return a representation of this frame and its context managers as a series of standard
traceback.FrameSummaryobjects.The last yielded
FrameSummarymatches whatas_stdlib_summary()would return. Before that, one or moreFrameSummaryobjects will be yielded for each of the activecontextsin this frame. Each context will get oneFrameSummaryintroducing it (pointing to the start of thewithorasync withblock), followed by zero or more frames containing any relevant substructure, such as elements in anExitStackor nested context managers within a@contextmanagerfunction. The order ofFrameSummaryobjects is intended to hew as closely as possible to the (reverse) path that an exception would take if it were to propagate up the call stack. That is, the result ofas_stdlib_summary_with_contexts()should ideally look pretty similar to what you would see when printing out a traceback after an exception.A
FrameSummarythat introduces a context will append some additional information (the type of the context manager and the name that its result was assigned to) to the function name in the returned object, in parentheses after a space. This results in reasonable output fromtraceback.StackSummary.format().By default, hidden frames (
Frame.hide) encountered during context manager traversal are not included in the output. You can use the show_hidden_frames parameter to override this. The frame on which you calledas_stdlib_summary_with_contexts()will be included unconditionally.If capture_locals is True, then the local
reprs will be included in eachFrameSummary, as withas_stdlib_summary(). Frame summaries that introduce a context will include the stringified context manager object as a fictitious local called"<context manager>".
- class stackscope.Context(obj: object, is_async: bool, is_exiting: bool = False, varname: str | None = None, start_line: int | None = None, description: str | None = None, inner_stack: Stack | None = None, children: Sequence[Context | Stack] = (), hide: bool = False)¶
Information about a context manager active within a frame.
- obj: object¶
The object that best describes what this context is doing. By default, this is the context manager object (the thing with the
__enter__/__aenter__and__exit__/__aexit__methods), but library glue may override it to provide something more helpful. For example, anasync with trio.open_nursery():block will put thetrio.Nurseryobject here instead of the context manager that wraps it.
- is_exiting: bool = False¶
True if this context manager is currently exiting, i.e., the next thing in the traceback is a call to its
__exit__or__aexit__.
- varname: str | None = None¶
The name that the result of the context manager was assigned to. In
with foo() as bar:, this is the string"bar". This may be an expression representing any valid assignment target, not just a simple identifier, although a simple identifier is by far the most common case. If the context manager result was not assigned anywhere, or if its assignment target was too complex for us to reconstruct, name will be None.
- start_line: int | None = None¶
The line number on which the
withorasync withblock started, orNoneif we couldn’t determine it. (In order to determine the corresponding filename, you need to know whichFramethisContextis associated with.)
- description: str | None = None¶
A description of the context manager suitable for human-readable output. By default this is None, meaning we don’t know how to do better than
repr(obj), but library glue may augment it in some cases, such as to provide the arguments that were passed to a@contextmanagerfunction.
- inner_stack: Stack | None = None¶
The call stack associated with the implementation of this context manager, if applicable. For a
@contextmanagerfunction, this will typically contain a single frame, though it might be more if the function usesyield from. In most other cases there are no associated frames so stack will be None.
- children: Sequence[Context | Stack] = ()¶
The other context managers or child task stacks that are logically nested inside this one, if applicable. For example, an
ExitStackwill have one entry here per thing that was pushed on the stack, and atrio.Nurserywill have one entry per child task running in the nursery.
- hide: bool = False¶
If true, this context manager relates to library internals that are likely to be more distracting than they are useful to see in a traceback. Analogous to the
__tracebackhide__variable supported by pytest. Hidden context managers are suppressed by default when printing stacks, but this can be controlled using the show_hidden_frames argument toformat().
- format(*, ascii_only: bool = False, show_contexts: bool = True, show_hidden_frames: bool = False) List[str]¶
Return a list of newline-terminated strings describing this object, which may be printed for human consumption.
str(obj)is equivalent to"".join(obj.format()).- Parameters:
ascii_only – Use only ASCII characters in the output. By default, Unicode line-drawing characters are used.
show_contexts – Include information about context managers in the output. This is the default; pass False for a shorter stack trace that only includes frames in the main series.
show_hidden_frames – Include frames in the output even if they are marked as hidden. By default, hidden frames will be suppressed. See
Frame.hidefor more details.