Source code for mlprodict.testing.verify_code

"""
Looks into the code and detects error
before finalizing the benchmark.


:githublink:`%|py|6`
"""
import ast


[docs]class ImperfectPythonCode(RuntimeError): """ Raised if the code shows errors. :githublink:`%|py|12` """ pass
[docs]def verify_code(source, exc=True): """ Verifies :epkg:`python` code. :param source: source to look into :param exc: raise an exception or return the list of missing identifiers :return: tuple(missing identifiers, :class:`CodeNodeVisitor`) :githublink:`%|py|24` """ node = ast.parse(source) v = CodeNodeVisitor() v.visit(node) assign = v._assign imports = v._imports names = v._names args = v._args known = {'super': None, 'ImportError': None} for kn in imports: known[kn[0]] = kn for kn in assign: known[kn[0]] = kn for kn in args: known[kn[0]] = kn issues = set() for name in names: if name[0] not in known: issues.add(name[0]) if exc and len(issues) > 0: raise ImperfectPythonCode( "Unknown identifiers: {} in\n{}".format( ",".join(issues), source)) return issues, v
[docs]class CodeNodeVisitor(ast.NodeVisitor): """ Visits the code, implements verification rules. :githublink:`%|py|53` """
[docs] def __init__(self): ast.NodeVisitor.__init__(self) self._rows = [] self._indent = 0 self._stack = [] self._imports = [] self._names = [] self._alias = [] self._assign = [] self._args = [] self._fits = []
[docs] def push(self, row): """ Pushes an element into a list. :githublink:`%|py|70` """ self._rows.append(row)
[docs] def generic_visit(self, node): """ Overrides ``generic_visit`` to check it is not used. :githublink:`%|py|76` """ raise AttributeError( "generic_visit_args should be used.") # pragma: no cover
[docs] def generic_visit_args(self, node, row): """ Overrides ``generic_visit`` to keep track of the indentation and the node parent. The function will add field ``row["children"] = visited`` nodes from here. :param node: node which needs to be visited :param row: row (a dictionary) :return: See ``ast.NodeVisitor.generic_visit`` :githublink:`%|py|89` """ self._indent += 1 last = len(self._rows) res = ast.NodeVisitor.generic_visit( # pylint: disable=E1111 self, node) # pylint: disable=E1111 row["children"] = [ _ for _ in self._rows[ last:] if _["indent"] == self._indent] self._indent -= 1 return res
[docs] def visit(self, node): """ Visits a node, a method must exist for every object class. :githublink:`%|py|103` """ method = 'visit_' + node.__class__.__name__ visitor = getattr(self, method, None) if visitor is None: if method.startswith("visit_"): cont = { "indent": self._indent, "str": method[6:], "node": node} self.push(cont) return self.generic_visit_args(node, cont) raise TypeError("unable to find a method: " + method) # pragma: no cover res = visitor(node) # print(method, CodeNodeVisitor.print_node(node)) return res
[docs] @staticmethod def print_node(node): """ Debugging purpose. :githublink:`%|py|124` """ r = [] for att in ["s", "name", "str", "id", "body", "n", "arg", "targets", "attr", "returns", "ctx"]: if att in node.__dict__: r.append("{0}={1}".format(att, str(node.__dict__[att]))) return " ".join(r)
[docs] def print_tree(self): # pylint: disable=C0116 """ Displays the tree of instructions. :return: string :githublink:`%|py|137` """ rows = [] for r in self.Rows: rows.append( ("{0}{1}: {2}".format( " " * r["indent"], r["type"], r["str"]))) return "\n".join(rows)
@property def Rows(self): """ returns a list of dictionaries with all the elements of the code :githublink:`%|py|152` """ return [_ for _ in self._rows if not _.get("remove", False)] def visit_Str(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Str", "str": node.s, "node": node, "value": node.s} self.push(cont) return self.generic_visit_args(node, cont) def visit_Name(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Name", "str": node.id, "node": node, "id": node.id, "ctx": node.ctx} self.push(cont) self._names.append((node.id, node)) return self.generic_visit_args(node, cont) def visit_Expr(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Expr", "str": '', "node": node, "value": node.value} self.push(cont) return self.generic_visit_args(node, cont) def visit_alias(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "alias", "str": "", "node": node, "name": node.name, "asname": node.asname} self.push(cont) self._alias.append((node.name, node.asname, node)) return self.generic_visit_args(node, cont) def visit_Module(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Module", "str": "", "body": node.body, "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Import(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Import", "str": "", "names": node.names, "node": node} self.push(cont) for name in node.names: self._imports.append((name.name, name.asname, node)) return self.generic_visit_args(node, cont) def visit_ImportFrom(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "ImportFrom", "str": "", "module": node.module, "names": node.names, "node": node} self.push(cont) for name in node.names: self._imports.append((name.name, name.asname, node.module, node)) return self.generic_visit_args(node, cont) def visit_ClassDef(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "ClassDef", "str": "", "name": node.name, "body": node.body, "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_FunctionDef(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "FunctionDef", "str": node.name, "name": node.name, "body": node.body, "node": node, "returns": node.returns} self.push(cont) return self.generic_visit_args(node, cont) def visit_arguments(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "arguments", "str": "", "node": node, "args": node.args} self.push(cont) return self.generic_visit_args(node, cont) def visit_arg(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "arg", "str": node.arg, "node": node, "arg": node.arg, "annotation": node.annotation} self.push(cont) self._args.append((node.arg, node)) return self.generic_visit_args(node, cont) def visit_Assign(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "Assign", "str": "", "node": node, "targets": node.targets, "value": node.value} self.push(cont) for t in node.targets: if hasattr(t, 'id'): self._assign.append((t.id, node)) else: self._assign.append((id(t), node)) return self.generic_visit_args(node, cont) def visit_Store(self, node): # pylint: disable=C0116 #cont = { "indent":self._indent, "type": "Store", "str": "" } # self.push(cont) cont = {} return self.generic_visit_args(node, cont) def visit_Call(self, node): # pylint: disable=C0116 if "attr" in node.func.__dict__: cont = {"indent": self._indent, "type": "Call", "str": node.func.attr, "node": node, "func": node.func} elif "id" in node.func.__dict__: cont = {"indent": self._indent, "type": "Call", "str": node.func.id, "node": node, "func": node.func} else: cont = {"indent": self._indent, "type": "Call", "str": "", # pragma: no cover "node": node, "func": node.func} self.push(cont) if cont['str'] == 'fit': self._fits.append(cont) return self.generic_visit_args(node, cont) def visit_Attribute(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "Attribute", "str": node.attr, "node": node, "value": node.value, "ctx": node.ctx, "attr": node.attr} self.push(cont) # last = len(self._rows) res = self.generic_visit_args(node, cont) if len(cont["children"]) > 0: fir = cont["children"][0] if 'type' in fir and fir["type"] == "Name": parent = fir["node"].id cont["str"] = "{0}.{1}".format(parent, cont["str"]) cont["children"][0]["remove"] = True return res def visit_Load(self, node): # pylint: disable=C0116 cont = {} return self.generic_visit_args(node, cont) def visit_keyword(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "keyword", "str": "{0}".format(node.arg), "node": node, "arg": node.arg, "value": node.value} self.push(cont) return self.generic_visit_args(node, cont) def visit_BinOp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "BinOp", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_UnaryOp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "UnaryOp", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Not(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Not", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Invert(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Invert", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_BoolOp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "BoolOp", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Mult(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Mult", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Div(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Div", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_FloorDiv(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "FloorDiv", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Add(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Add", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Pow(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Pow", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_In(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "In", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_AugAssign(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "AugAssign", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Eq(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Eq", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_IsNot(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "IsNot", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Is(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Is", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_And(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "And", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_BitAnd(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "BitAnd", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Or(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Or", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_NotEq(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "NotEq", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Mod(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Mod", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Sub(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Sub", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_USub(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "USub", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Compare(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Compare", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Gt(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "Gt", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_GtE(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "GtE", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Lt(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "Lt", "str": "", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Num(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Num", "node": node, "str": "{0}".format( node.n), 'n': node.n} self.push(cont) return self.generic_visit_args(node, cont) def visit_Return(self, node): # pylint: disable=C0116 cont = {"indent": self._indent, "type": "Return", "node": node, "str": "", 'value': node.value} self.push(cont) return self.generic_visit_args(node, cont) def visit_List(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "List", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_ListComp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "ListComp", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_comprehension(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "comprehension", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Dict(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Dict", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Tuple(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "Tuple", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_NameConstant(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "type": "NameConstant", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_(self, node): # pylint: disable=C0116 raise RuntimeError( # pragma: no cover "This node is not handled: {}".format(node)) def visit_Subscript(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "Subscript", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_ExtSlice(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "ExtSlice", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Slice(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "Slice", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Index(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "Index", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_If(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "If", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_IfExp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "IfExp", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_Lambda(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "Lambda", "node": node} self.push(cont) return self.generic_visit_args(node, cont) def visit_GeneratorExp(self, node): # pylint: disable=C0116 cont = { "indent": self._indent, "str": "GeneratorExp", "node": node} self.push(cont) return self.generic_visit_args(node, cont)