| import inspect |
| import os |
| |
| try: |
| import trace |
| except ImportError: |
| pass |
| else: |
| trace._warn = lambda *args: None # workaround for http://bugs.python.org/issue17143 (PY-8706) |
| |
| import gc |
| from pydevd_comm import CMD_SIGNATURE_CALL_TRACE, NetCommand |
| import pydevd_vars |
| from pydevd_constants import xrange |
| |
| class Signature(object): |
| def __init__(self, file, name): |
| self.file = file |
| self.name = name |
| self.args = [] |
| self.args_str = [] |
| |
| def add_arg(self, name, type): |
| self.args.append((name, type)) |
| self.args_str.append("%s:%s"%(name, type)) |
| |
| def __str__(self): |
| return "%s %s(%s)"%(self.file, self.name, ", ".join(self.args_str)) |
| |
| |
| class SignatureFactory(object): |
| def __init__(self): |
| self._caller_cache = {} |
| self.project_roots = os.getenv('PYCHARM_PROJECT_ROOTS', '').split(os.pathsep) |
| |
| def is_in_scope(self, filename): |
| filename = os.path.normcase(filename) |
| for root in self.project_roots: |
| root = os.path.normcase(root) |
| if filename.startswith(root): |
| return True |
| return False |
| |
| |
| |
| def create_signature(self, frame): |
| try: |
| code = frame.f_code |
| locals = frame.f_locals |
| filename, modulename, funcname = self.file_module_function_of(frame) |
| res = Signature(filename, funcname) |
| for i in xrange(0, code.co_argcount): |
| name = code.co_varnames[i] |
| tp = type(locals[name]) |
| class_name = tp.__name__ |
| if class_name == 'instance': # old-style classes |
| tp = locals[name].__class__ |
| class_name = tp.__name__ |
| |
| if tp.__module__ and tp.__module__ != '__main__': |
| class_name = "%s.%s"%(tp.__module__, class_name) |
| |
| res.add_arg(name, class_name) |
| return res |
| except: |
| import traceback |
| traceback.print_exc() |
| |
| |
| def file_module_function_of(self, frame): #this code is take from trace module and fixed to work with new-style classes |
| code = frame.f_code |
| filename = code.co_filename |
| if filename: |
| modulename = trace.modname(filename) |
| else: |
| modulename = None |
| |
| funcname = code.co_name |
| clsname = None |
| if code in self._caller_cache: |
| if self._caller_cache[code] is not None: |
| clsname = self._caller_cache[code] |
| else: |
| self._caller_cache[code] = None |
| ## use of gc.get_referrers() was suggested by Michael Hudson |
| # all functions which refer to this code object |
| funcs = [f for f in gc.get_referrers(code) |
| if inspect.isfunction(f)] |
| # require len(func) == 1 to avoid ambiguity caused by calls to |
| # new.function(): "In the face of ambiguity, refuse the |
| # temptation to guess." |
| if len(funcs) == 1: |
| dicts = [d for d in gc.get_referrers(funcs[0]) |
| if isinstance(d, dict)] |
| if len(dicts) == 1: |
| classes = [c for c in gc.get_referrers(dicts[0]) |
| if hasattr(c, "__bases__") or inspect.isclass(c)] |
| elif len(dicts) > 1: #new-style classes |
| classes = [c for c in gc.get_referrers(dicts[1]) |
| if hasattr(c, "__bases__") or inspect.isclass(c)] |
| else: |
| classes = [] |
| |
| if len(classes) == 1: |
| # ditto for new.classobj() |
| clsname = classes[0].__name__ |
| # cache the result - assumption is that new.* is |
| # not called later to disturb this relationship |
| # _caller_cache could be flushed if functions in |
| # the new module get called. |
| self._caller_cache[code] = clsname |
| |
| |
| if clsname is not None: |
| funcname = "%s.%s" % (clsname, funcname) |
| |
| return filename, modulename, funcname |
| |
| def create_signature_message(signature): |
| cmdTextList = ["<xml>"] |
| |
| cmdTextList.append('<call_signature file="%s" name="%s">' % (pydevd_vars.makeValidXmlValue(signature.file), pydevd_vars.makeValidXmlValue(signature.name))) |
| |
| for arg in signature.args: |
| cmdTextList.append('<arg name="%s" type="%s"></arg>' % (pydevd_vars.makeValidXmlValue(arg[0]), pydevd_vars.makeValidXmlValue(arg[1]))) |
| |
| cmdTextList.append("</call_signature></xml>") |
| cmdText = ''.join(cmdTextList) |
| return NetCommand(CMD_SIGNATURE_CALL_TRACE, 0, cmdText) |
| |
| def sendSignatureCallTrace(dbg, frame, filename): |
| if dbg.signature_factory.is_in_scope(filename): |
| dbg.writer.addCommand(create_signature_message(dbg.signature_factory.create_signature(frame))) |
| |
| |
| |