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 a StackSlice, coroutine object, generator iterator, thread, greenlet, trio.lowlevel.Task, or anything else for which a library author (maybe you) have added support through the unwrap_stackitem() customization hook.

  • extract_since() and extract_until() obtain a stack for the callees or callers (respectively) of a currently-executing frame. Use extract_since(None) to get the full stack of the thread that made the extract_since() call, like inspect.stack(). These are aliases for invoking extract() with a StackSlice.

  • extract_outermost() returns the outermost Frame that extract() 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 use extract_since() or extract_until(), which are shortcuts for passing a StackSlice to extract(). 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 the unwrap_stackitem hook.

If with_contexts is True (the default), then each returned Frame will have a contexts attribute specifying the context managers that are currently active in that frame. If you don’t care about this information, then specifying with_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 as Context.children in the returned Frame.contexts for these context managers. If recurse_child_tasks is False (the default), then these “child tasks” will be rendered as stub Stack objects with only a root (the child task object) but no frames. 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 the error attribute of the returned object. If multiple errors are encountered, they will be wrapped in an ExceptionGroup.

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 to extract(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 a Stack containing information only on outer_frame itself; depending on the situation, its error 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)) or extract(StackSlice(outer=outer_frame, inner=limit)) depending on the type of limit, except that extract_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 calling extract() and returning the first Frame of the returned stack, but might be faster since it can stop once it’s extracted one frame. If the result has no Frames, 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 to extract() or return from an unwrap_stackitem() hook.

This can be used in three different ways:

  • If inner is not None, then the StackSlice logically contains currently-executing frames that are direct or indirect callers of inner, ending with inner itself. Iteration begins with inner and proceeds outward via frame.f_back links until the frame outer is reached or limit frames have been extracted. If neither of those is specified, then the stack slice starts with the outermost frame of the thread on which inner is running.

    If inner 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 and not any of its callers.

  • If inner is None but outer is not, the StackSlice contains outer followed by its currently-executing direct and indirect callees, up to limit frames total.

    If outer is executing on the current thread, then the StackSlice ends with the frame that called stackscope.extract() (unless it is cut off before that by reaching its limit). If it is executing on some other thread, and remains so throughout the stack extraction process, then the StackSlice ends with the innermost frame on that thread. In any other case – if outer belongs to a suspended coroutine, generator, greenlet, or if it starts or stops running on another thread while stackscope.extract() is executing – the returned stack will contain information only on outer itself.

  • If inner and outer are both None, the StackSlice contains the entirety of the current thread’s stack, ending with the frame that made the call to stackscope.extract().

outer: types.FrameType | None = None

The outermost frame to extract. If unspecified, start from inner (or the caller of stackscope.extract() if inner is None) and iterate outward until top-of-stack is reached or limit frames have been extracted.

inner: types.FrameType | None = None

The innermost frame to extract. If unspecified, extract all the currently-executing callees of outer, up to limit frames total.

limit: int | None = None

The maximum number of frames to extract. If None, there is no limit.

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 its frames, and its len() 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, or None if the trace was created from a StackSlice (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 of extract().)

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 the Stack object, a StackSummary 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 the Frame 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 original frame.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 or async 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 to format().

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 with hide, you can force the hidden information to be displayed by specifying the show_hidden_frames argument to format().

property filename: str

The filename of the Python file from which the code executing in this frame was imported.

property funcname: str

The name of the function executing in this frame.

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 or cls, 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 through sys.modules looking for a module whose __file__ matches this frame’s filename.

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 the Frame object, a FrameSummary 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 passing capture_locals=True to traceback.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 what as_stdlib_summary() would return. Before that, one or more FrameSummary objects will be yielded for each of the active contexts in this frame. Each context will get one FrameSummary introducing it (pointing to the start of the with or async with block), followed by zero or more frames containing any relevant substructure, such as elements in an ExitStack or nested context managers within a @contextmanager function. The order of FrameSummary 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 of as_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 from traceback.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 called as_stdlib_summary_with_contexts() will be included unconditionally.

If capture_locals is True, then the local reprs will be included in each FrameSummary, as with as_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, an async with trio.open_nursery(): block will put the trio.Nursery object here instead of the context manager that wraps it.

is_async: bool

True for an async context manager, False for a sync context manager.

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 or async with block started, or None if we couldn’t determine it. (In order to determine the corresponding filename, you need to know which Frame this Context 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 uses yield 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 a trio.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 to format().

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.