Skip to content

Integration of stack_data #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ dist: xenial # required for Python >= 3.7

language: python
python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"

env:
# - NUMPY_VERSION='numpy==1.13'
- NUMPY_VERSION='numpy'
- "3.8-dev"

# command to install dependencies
install:
- pip install -q $NUMPY_VERSION
- pip install .[tests]

# command to run tests
script:
Expand Down
9 changes: 8 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
with open("README.md", "r") as fh:
long_description = fh.read()

test_requires = ["pytest", "numpy"]

setuptools.setup(
python_requires=">=3.4",
python_requires=">=3.5",
install_requires=["stack_data"],
test_requires=test_requires,
extras_require={
'tests': test_requires,
},
name="stackprinter",
version="0.2.3",
author="cknd",
Expand Down
191 changes: 3 additions & 188 deletions stackprinter/extraction.py
Original file line number Diff line number Diff line change
@@ -1,193 +1,8 @@
import types
import inspect
from collections import OrderedDict, namedtuple
from stackprinter.source_inspection import annotate
from stack_data import FrameInfo, Options

NON_FUNCTION_SCOPES = ['<module>', '<lambda>', '<listcomp>']

_FrameInfo = namedtuple('_FrameInfo',
['filename', 'function', 'lineno', 'source_map',
'head_lns', 'line2names', 'name2lines', 'assignments'])

class FrameInfo(_FrameInfo):
# give this namedtuple type a friendlier string representation
def __str__(self):
return ("<FrameInfo %s, line %s, scope %s>" %
(self.filename, self.lineno, self.function))


def get_info(tb_or_frame, lineno=None):
"""
Get a frame representation that's easy to format


Params
---
tb: Traceback object or Frame object

lineno: int (optional)
Override which source line is treated as the important one. For trace-
back objects this defaults to the last executed line (tb.tb_lineno).
For frame objects, it defaults the currently executed one (fr.f_lineno).


Returns
---
FrameInfo, a named tuple with the following fields:

filename: Path of the executed source file

function: Name of the scope

lineno: Highlighted line (last executed line)

source_map: OrderedDict
Maps line numbers to a list of tokens. Each token is a (string, type)
tuple. Concatenating the first elements of all tokens of all lines
restores the original source, weird whitespaces/indentations and all
(in contrast to python's built-in `tokenize`). However, multiline
statements (those with a trailing backslash) are secretly collapsed
into their first line.

head_lns: (int, int) or (None, None)
Line numbers of the beginning and end of the function header

line2names: dict
Maps each line number to a list of variables names that occur there

name2lines: dict
Maps each variable name to a list of line numbers where it occurs

assignments: OrderedDict
Holds current values of all variables that occur in the source and
are found in the given frame's locals or globals. Attribute lookups
with dot notation are treated as one variable, so if `self.foo.zup`
occurs in the source, this dict will get a key 'self.foo.zup' that
holds the fully resolved value.
(TODO: it would be easy to return the whole attribute lookup chain,
so maybe just do that & let formatting decide which parts to show?)
(TODO: Support []-lookups just like . lookups)
"""

def get_info(tb_or_frame):
if isinstance(tb_or_frame, FrameInfo):
return tb_or_frame

if isinstance(tb_or_frame, types.TracebackType):
tb = tb_or_frame
lineno = tb.tb_lineno if lineno is None else lineno
frame = tb.tb_frame
elif isinstance(tb_or_frame, types.FrameType):
frame = tb_or_frame
lineno = frame.f_lineno if lineno is None else lineno
else:
raise ValueError('Cant inspect this: ' + repr(tb_or_frame))

filename = inspect.getsourcefile(frame) or inspect.getfile(frame)
function = frame.f_code.co_name

try:
source, startline = get_source(frame)
# this can be slow (tens of ms) the first time it is called, since
# inspect.get_source internally calls inspect.getmodule, for no
# other purpose than updating the linecache. seems like a bad tradeoff
# for our case, but this is not the time & place to fork `inspect`.
except:
source = []
startline = lineno

source_map, line2names, name2lines, head_lns, lineno = annotate(source, startline, lineno)

if function in NON_FUNCTION_SCOPES:
head_lns = []

names = name2lines.keys()
assignments = get_vars(names, frame.f_locals, frame.f_globals)

finfo = FrameInfo(filename, function, lineno, source_map, head_lns,
line2names, name2lines, assignments)
return finfo


def get_source(frame):
"""
get source lines for this frame

Params
---
frame : frame object

Returns
---
lines : list of str

startline : int
location of lines[0] in the original source file
"""

# TODO find out what's faster: Allowing inspect's getsourcelines
# to tokenize the whole file to find the surrounding code block,
# or getting the whole file quickly via linecache & feeding all
# of it to our own instance of tokenize, then clipping to
# desired context afterwards.

if frame.f_code.co_name in NON_FUNCTION_SCOPES:
lines, _ = inspect.findsource(frame)
startline = 1
else:
lines, startline = inspect.getsourcelines(frame)

return lines, startline


def get_vars(names, loc, glob):
assignments = []
for name in names:
try:
val = lookup(name, loc, glob)
except LookupError:
pass
else:
assignments.append((name, val))
return OrderedDict(assignments)


def lookup(name, scopeA, scopeB):
basename, *attr_path = name.split('.')
if basename in scopeA:
val = scopeA[basename]
elif basename in scopeB:
val = scopeB[basename]
else:
# not all names in the source file will be
# defined (yet) when we get to see the frame
raise LookupError(basename)

for k, attr in enumerate(attr_path):
try:
val = getattr(val, attr)
except Exception as e:
# return a special value in case of lookup errors
# (note: getattr can raise anything, e.g. if a complex
# @property fails).
return UnresolvedAttribute(basename, attr_path, k, val,
e.__class__.__name__, str(e))
return val


class UnresolvedAttribute():
"""
Container value for failed dot attribute lookups
"""
def __init__(self, basename, attr_path, failure_idx, value,
exc_type, exc_str):
self.basename = basename
self.attr_path = attr_path
self.first_failed = attr_path[failure_idx]
self.failure_idx = failure_idx
self.last_resolvable_value = value
self.exc_type = exc_type
self.exc_str = exc_str

@property
def last_resolvable_name(self):
return self.basename + '.'.join([''] + self.attr_path[:self.failure_idx])
return FrameInfo(tb_or_frame, Options(include_signature=True))
14 changes: 5 additions & 9 deletions stackprinter/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import types
import traceback

import stack_data

import stackprinter.extraction as ex
import stackprinter.colorschemes as colorschemes
from stackprinter.utils import match, get_ansi_tpl
Expand Down Expand Up @@ -71,8 +73,7 @@ def format_stack(frames, style='plaintext', source_lines=5,
frame_msgs = []
parent_is_boring = True
for frame in frames:
fi = ex.get_info(frame)
is_boring = match(fi.filename, suppressed_paths)
is_boring = match(frame.filename, suppressed_paths)
if is_boring:
if parent_is_boring:
formatter = minimal_formatter
Expand All @@ -82,7 +83,7 @@ def format_stack(frames, style='plaintext', source_lines=5,
formatter = verbose_formatter

parent_is_boring = is_boring
frame_msgs.append(formatter(fi))
frame_msgs.append(formatter(frame))

if reverse:
frame_msgs = reversed(frame_msgs)
Expand All @@ -98,12 +99,7 @@ def format_stack_from_frame(fr, add_summary=False, **kwargs):
keyword args like stackprinter.format()

"""
stack = []
while fr is not None:
stack.append(fr)
fr = fr.f_back
stack = reversed(stack)

stack = stack_data.FrameInfo.stack_data(fr, collapse_repeated_frames=False)
return format_stack(stack, **kwargs)


Expand Down
Loading