def _db_func(self, data, filename, html_body, name, start_lineno): """ Retrieve the Function object from the database if one exists, or create one. """ function_hash = hashlib.sha256((filename + name + html_body + data + str(start_lineno) ).encode('utf8')).hexdigest() db_func = one_or_none(session.query(Function).filter_by(hash=function_hash)) # type: Optional[Function] if not db_func: db_func = Function(file=filename, name=name, html_body=html_body, lineno=start_lineno, data=data, hash=function_hash) session.add(db_func) session.commit() return db_func
def __call__(self, func): if inspect.isclass(func): cls = func for name, meth in iteritems(cls.__dict__): if inspect.ismethod(meth) or inspect.isfunction(meth): setattr(cls, name, self.__call__(meth)) return cls new_func = super(BirdsEye, self).__call__(func) code_info = self._code_infos.get(new_func.__code__) if code_info: return new_func lines, start_lineno = inspect.getsourcelines(func) end_lineno = start_lineno + len(lines) name = safe_qualname(func) filename = os.path.abspath(inspect.getsourcefile(func)) traced_file = new_func.traced_file traced_file.root._depth = 0 for node in ast.walk(traced_file.root): for child in ast.iter_child_nodes(node): child._depth = node._depth + 1 positions = [] node_loops = {} for node in traced_file.nodes: if isinstance(node, ast.expr): node_type = 'expr' if not node._is_interesting_expression: continue elif (isinstance(node, (ast.While, ast.For, ast.comprehension)) and not isinstance(node.parent, ast.GeneratorExp)): node_type = 'loop' elif isinstance(node, ast.stmt): node_type = 'stmt' else: continue assert isinstance(node, ast.AST) # In particular FormattedValue is missing this if not hasattr(node, 'first_token'): continue if not start_lineno <= node.first_token.start[0] <= end_lineno: continue start, end = traced_file.tokens.get_text_range(node) if start == end == 0: continue positions.append((start, 1, node._depth, '<span data-index="%s" data-type="%s">' % (node._tree_index, node_type))) positions.append((end, 0, node._depth, '</span>')) if node._loops: node_loops[node._tree_index] = [ n._tree_index for n in node._loops ] comprehensions = group_by_key_func([ comp for comp in traced_file.nodes if isinstance(comp, ast.comprehension) ], lambda c: c.first_token.line) def get_start(n): return traced_file.tokens.get_text_range(n)[0] for comp_list in comprehensions.values(): prev_start = None for comp in sorted(comp_list, key=lambda c: c.first_token.startpos): if comp is comp.parent.generators[0]: start = get_start(comp.parent) if prev_start is not None and start < prev_start: start = get_start(comp) else: start = get_start(comp) if prev_start is not None: positions.append((start, 1, 0, '\n ')) end_lineno += 1 prev_start = start positions.append((len(traced_file.source), None, None, '')) positions.sort() html_body = [] start = 0 for pos, _, _, part in positions: html_body.append(html.escape(traced_file.source[start:pos])) html_body.append(part) start = pos html_body = ''.join(html_body) html_body = '\n'.join( html_body.split('\n')[start_lineno - 1:end_lineno - 1]) db_args = dict(file=filename, name=name, html_body=html_body, lineno=start_lineno, data=json.dumps( dict(node_loops=node_loops, ), sort_keys=True, )) db_func = one_or_none(session.query(Function).filter_by(**db_args)) if not db_func: db_func = Function(**db_args) session.add(db_func) session.commit() self._code_infos[new_func.__code__] = CodeInfo(db_func, traced_file) return new_func