""" Imitate the parser representation. """ import inspect import re import sys import os from jedi._compatibility import builtins as _builtins from jedi import debug from jedi.cache import underscore_memoization from jedi.evaluate.sys_path import get_sys_path from jedi.parser.representation import Param, SubModule, Base, IsScope, Operator from jedi.evaluate.helpers import FakeName from . import fake class CompiledObject(Base): # comply with the parser start_pos = 0, 0 asserts = [] path = None # modules have this attribute - set it to None. def __init__(self, obj, parent=None): self.obj = obj self.parent = parent self.doc = inspect.getdoc(obj) or '' @property def params(self): params_str, ret = self._parse_function_doc() tokens = params_str.split(',') params = [] module = SubModule(self.get_parent_until().name) # it seems like start_pos/end_pos is always (0, 0) for a compiled # object start_pos, end_pos = (0, 0), (0, 0) for p in tokens: parts = [FakeName(part) for part in p.strip().split('=')] if len(parts) >= 2: parts.insert(1, Operator('=', (0, 0))) params.append(Param(module, parts, start_pos, end_pos, builtin)) return params def __repr__(self): return '<%s: %s>' % (type(self).__name__, repr(self.obj)) @underscore_memoization def _parse_function_doc(self): if self.doc is None: return '', '' return _parse_function_doc(self.doc) def type(self): cls = self._cls().obj if inspect.isclass(cls): return 'class' elif inspect.ismodule(cls): return 'module' elif inspect.isbuiltin(cls) or inspect.ismethod(cls) \ or inspect.ismethoddescriptor(cls): return 'function' def is_executable_class(self): return inspect.isclass(self.obj) @underscore_memoization def _cls(self): # Ensures that a CompiledObject is returned that is not an instance (like list) if fake.is_class_instance(self.obj): try: c = self.obj.__class__ except AttributeError: # happens with numpy.core.umath._UFUNC_API (you get it # automatically by doing `import numpy`. c = type(None) return CompiledObject(c, self.parent) return self def get_defined_names(self): cls = self._cls() for name in dir(cls.obj): yield CompiledName(cls, name) def instance_names(self): return self.get_defined_names() def get_subscope_by_name(self, name): if name in dir(self._cls().obj): return CompiledName(self._cls(), name).parent else: raise KeyError("CompiledObject doesn't have an attribute '%s'." % name) def get_index_types(self, index_types): # If the object doesn't have `__getitem__`, just raise the # AttributeError. self.obj.__getitem__ result = [] from jedi.evaluate import iterable for typ in index_types: if isinstance(typ, iterable.Slice): result.append(self) else: try: new = self.obj[typ.obj] except (KeyError, IndexError, TypeError, AttributeError): pass # just try, we don't care if it fails. else: result.append(CompiledObject(new)) if not result: try: for obj in self.obj: result.append(CompiledObject(obj)) except TypeError: pass # self.obj maynot have an __iter__ method. return result @property def name(self): # might not exist sometimes (raises AttributeError) return self._cls().obj.__name__ def execute_function(self, evaluator, params): if self.type() != 'function': return for name in self._parse_function_doc()[1].split(): try: bltn_obj = _create_from_name(builtin, builtin, name) except AttributeError: continue else: if isinstance(bltn_obj, CompiledObject): # We want everything except None. if bltn_obj.obj is not None: yield bltn_obj else: for result in evaluator.execute(bltn_obj, params): yield result @property @underscore_memoization def subscopes(self): """ Returns only the faked scopes - the other ones are not important for internal analysis. """ module = self.get_parent_until() faked_subscopes = [] for name in dir(self._cls().obj): f = fake.get_faked(module.obj, self.obj, name) if f: f.parent = self faked_subscopes.append(f) return faked_subscopes def get_self_attributes(self): return [] # Instance compatibility def get_imports(self): return [] # Builtins don't have imports def is_callable(self): """Check if the object has a ``__call__`` method.""" return hasattr(self.obj, '__call__') class CompiledName(FakeName): def __init__(self, obj, name): super(CompiledName, self).__init__(name) self._obj = obj self.name = name self.start_pos = 0, 0 # an illegal start_pos, to make sorting easy. def __repr__(self): return '<%s: (%s).%s>' % (type(self).__name__, self._obj.name, self.name) @property @underscore_memoization def parent(self): module = self._obj.get_parent_until() return _create_from_name(module, self._obj, self.name) @parent.setter def parent(self, value): pass # Just ignore this, FakeName tries to overwrite the parent attribute. def load_module(path, name): if not name: name = os.path.basename(path) name = name.rpartition('.')[0] # cut file type (normally .so) # sometimes there are endings like `_sqlite3.cpython-32mu` name = re.sub(r'\..*', '', name) dot_path = [] if path: p = path # if path is not in sys.path, we need to make a well defined import # like `from numpy.core import umath.` while p and p not in sys.path: p, sep, mod = p.rpartition(os.path.sep) dot_path.insert(0, mod.partition('.')[0]) if p: name = ".".join(dot_path) path = p else: path = os.path.dirname(path) sys_path = get_sys_path() if path: sys_path.insert(0, path) temp, sys.path = sys.path, sys_path try: module = __import__(name, {}, {}, dot_path[:-1]) except AttributeError: # use sys.modules, because you cannot access some modules # directly. -> github issue #59 module = sys.modules[name] sys.path = temp return CompiledObject(module) docstr_defaults = { 'floating point number': 'float', 'character': 'str', 'integer': 'int', 'dictionary': 'dict', 'string': 'str', } def _parse_function_doc(doc): """ Takes a function and returns the params and return value as a tuple. This is nothing more than a docstring parser. TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None TODO docstrings like 'tuple of integers' """ # parse round parentheses: def func(a, (b,c)) try: count = 0 start = doc.index('(') for i, s in enumerate(doc[start:]): if s == '(': count += 1 elif s == ')': count -= 1 if count == 0: end = start + i break param_str = doc[start + 1:end] except (ValueError, UnboundLocalError): # ValueError for doc.index # UnboundLocalError for undefined end in last line debug.dbg('no brackets found - no param') end = 0 param_str = '' else: # remove square brackets, that show an optional param ( = None) def change_options(m): args = m.group(1).split(',') for i, a in enumerate(args): if a and '=' not in a: args[i] += '=None' return ','.join(args) while True: param_str, changes = re.subn(r' ?\[([^\[\]]+)\]', change_options, param_str) if changes == 0: break param_str = param_str.replace('-', '_') # see: isinstance.__doc__ # parse return value r = re.search('-[>-]* ', doc[end:end + 7]) if r is None: ret = '' else: index = end + r.end() # get result type, which can contain newlines pattern = re.compile(r'(,\n|[^\n-])+') ret_str = pattern.match(doc, index).group(0).strip() # New object -> object() ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str) ret = docstr_defaults.get(ret_str, ret_str) return param_str, ret class Builtin(CompiledObject, IsScope): def get_defined_names(self): # Filter None, because it's really just a keyword, nobody wants to # access it. return [d for d in super(Builtin, self).get_defined_names() if d.name != 'None'] def _a_generator(foo): """Used to have an object to return for generators.""" yield 42 yield foo builtin = Builtin(_builtins) magic_function_class = CompiledObject(type(load_module), parent=builtin) generator_obj = CompiledObject(_a_generator(1.0)) def _create_from_name(module, parent, name): faked = fake.get_faked(module.obj, parent.obj, name) # only functions are necessary. if faked is not None: faked.parent = parent return faked try: obj = getattr(parent.obj, name) except AttributeError: # happens e.g. in properties of # PyQt4.QtGui.QStyleOptionComboBox.currentText # -> just set it to None obj = None return CompiledObject(obj, parent) def create(obj, parent=builtin, module=None): """ A very weird interface class to this module. The more options provided the more acurate loading compiled objects is. """ if not inspect.ismodule(obj): faked = fake.get_faked(module and module.obj, obj) if faked is not None: faked.parent = parent return faked return CompiledObject(obj, parent)