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 outermostFrame
thatextract()
would return, without computing the whole stack.
- stackscope.extract(stackitem: StackItem, *, with_contexts: bool = True, recurse_child_tasks: bool = False) Stack ¶
Extract a
Stack
from 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
StackSlice
or useextract_since()
orextract_until()
, which are shortcuts for passing aStackSlice
toextract()
. 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_stackitem
hook.If with_contexts is True (the default), then each returned
Frame
will have acontexts
attribute specifying the context managers that are currently active in that frame. If you don’t care about this information, then specifyingwith_contexts=False
will 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.children
in the returnedFrame.contexts
for these context managers. If recurse_child_tasks is False (the default), then these “child tasks” will be rendered as stubStack
objects 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 theerror
attribute 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
Stack
reflecting 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 aStack
containing information only on outer_frame itself; depending on the situation, itserror
member 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
Stack
reflecting 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_back
attribute, 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
Frame
from stackitem.extract_outermost()
produces the same result as callingextract()
and returning the firstFrame
of the returned stack, but might be faster since it can stop once it’s extracted one frame. If the result has noFrame
s, 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.
StackSlice
has 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
inner
is not None, then theStackSlice
logically contains currently-executing frames that are direct or indirect callers ofinner
, ending withinner
itself. Iteration begins withinner
and proceeds outward viaframe.f_back
links until the frameouter
is reached orlimit
frames have been extracted. If neither of those is specified, then the stack slice starts with the outermost frame of the thread on whichinner
is running.If
inner
belongs to a suspended coroutine or generator, or if it otherwise is not linked to other frames via itsf_back
attribute, then the returned traceback will contain onlyinner
and not any of its callers.If
inner
is None butouter
is not, theStackSlice
containsouter
followed by its currently-executing direct and indirect callees, up tolimit
frames total.If
outer
is executing on the current thread, then theStackSlice
ends 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 theStackSlice
ends with the innermost frame on that thread. In any other case – ifouter
belongs 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 onouter
itself.If
inner
andouter
are both None, theStackSlice
contains 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()
ifinner
is None) and iterate outward until top-of-stack is reached orlimit
frames 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
Stack
like 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, orNone
if 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. (
stackscope
does 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.hide
for 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 theStack
object, aStackSummary
can 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
yield
statement or greenlet switch, as captured when theFrame
object 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).pyframe
will recover the originalframe.pyframe
. For example, when traversing an async call stack,origin
might 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 (
with
orasync with
blocks) 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
hide
which 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
self
orcls
, 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.modules
looking 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.hide
for more details.
- as_stdlib_summary(*, capture_locals: bool = False) FrameSummary ¶
Return a representation of this frame entry as a standard
traceback.FrameSummary
object. Unlike theFrame
object, aFrameSummary
can be pickled and will not keep frames alive, at the expense of some loss of information.If capture_locals is True, then the returned
FrameSummary
will contain the stringified object representations of local variables in the frame, just like passingcapture_locals=True
totraceback.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.FrameSummary
objects.The last yielded
FrameSummary
matches whatas_stdlib_summary()
would return. Before that, one or moreFrameSummary
objects will be yielded for each of the activecontexts
in this frame. Each context will get oneFrameSummary
introducing it (pointing to the start of thewith
orasync with
block), followed by zero or more frames containing any relevant substructure, such as elements in anExitStack
or nested context managers within a@contextmanager
function. The order ofFrameSummary
objects 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
FrameSummary
that 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
repr
s 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.Nursery
object 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
with
orasync with
block started, orNone
if we couldn’t determine it. (In order to determine the corresponding filename, you need to know whichFrame
thisContext
is 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@contextmanager
function.
- inner_stack: Stack | None = None¶
The call stack associated with the implementation of this context manager, if applicable. For a
@contextmanager
function, 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
ExitStack
will have one entry here per thing that was pushed on the stack, and atrio.Nursery
will 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.hide
for more details.