diff --git a/README.mdown b/README.mdown index f4a08f15..2621d270 100644 --- a/README.mdown +++ b/README.mdown @@ -46,6 +46,7 @@ Jedi supports many of the widely used Python features: `__get__`, `__getitem__`, `__init__` - support for list.append, set.add, list.extend, etc. - (nested) list comprehensions / ternary expressions + - relative imports - function annotations (py3k feature, are ignored right now, but being parsed. I don't know what to do with them.) - class decorators (py3k feature, are being ignored too, until I find a use @@ -59,7 +60,6 @@ they are on my todo list): - manipulations of instances outside the instance variables, without using functions - mro - - relative imports - operation support -> \_\_mul\_\_, \_\_add\_\_, etc. - assert / isinstance diff --git a/evaluate.py b/evaluate.py index 80bc265a..fd68b3f7 100644 --- a/evaluate.py +++ b/evaluate.py @@ -11,8 +11,7 @@ statement. TODO magic methods: __mul__, __add__, etc. TODO evaluate asserts/isinstance (type safety) -python 3 stuff: -TODO nonlocal statement, needed or can be ignored? +TODO nonlocal statement, needed or can be ignored? (py3k) TODO __ instance attributes should not be visible outside of the class. TODO getattr / __getattr__ / __getattribute__ ? diff --git a/imports.py b/imports.py index f13001a0..8450767f 100644 --- a/imports.py +++ b/imports.py @@ -121,15 +121,22 @@ class ImportPath(object): def follow_file_system(self): """ Find a module with a path (of the module, like usb.backend.libusb10). - TODO: relative imports """ def follow_str(ns, string): debug.dbg('follow_module', ns, string) + path = None if ns: - return imp.find_module(string, [ns[1]]) + path = ns[1] + elif self.import_stmt.relative_count: + module = self.import_stmt.get_parent_until() + path = os.path.abspath(module.path) + for i in range(self.import_stmt.relative_count): + path = os.path.dirname(path) + + if path is not None: + return imp.find_module(string, [path]) else: - path = None - debug.dbg('search_module', string, path, self.file_path) + debug.dbg('search_module', string, self.file_path) # Override the sys.path. It works only good that way. # Injecting the path directly into `find_module` did not work. sys.path, temp = builtin.module_find_path, sys.path @@ -137,7 +144,6 @@ class ImportPath(object): sys.path = temp return i - # TODO handle relative paths - they are included in the import object current_namespace = None builtin.module_find_path.insert(0, self.file_path) # now execute those paths diff --git a/parsing.py b/parsing.py index 93cbbf1d..a7d15483 100644 --- a/parsing.py +++ b/parsing.py @@ -503,37 +503,38 @@ class Import(Simple): :param defunct: An Import is valid or not. :type defunct: bool """ - def __init__(self, start_pos, end_pos, namespace, alias='', from_ns='', \ - star=False, relative_count=None, defunct=False): + def __init__(self, start_pos, end_pos, namespace, alias=None, \ + from_ns=None, star=False, relative_count=None, defunct=False): super(Import, self).__init__(start_pos, end_pos) self.namespace = namespace - if namespace: - namespace.parent = weakref.ref(self) - self.alias = alias - if alias: - alias.parent = weakref.ref(self) - self.from_ns = from_ns - if from_ns: - from_ns.parent = weakref.ref(self) + for n in [namespace, alias, from_ns]: + if n: + n.parent = weakref.ref(self) self.star = star self.relative_count = relative_count self.defunct = defunct def get_code(self): + # in case one thing is None + alias = self.alias or '' + namespace = self.namespace or '' + from_ns = self.from_ns or '' + if self.alias: - ns_str = "%s as %s" % (self.namespace, self.alias) + ns_str = "%s as %s" % (namespace, alias) else: - ns_str = str(self.namespace) - if self.from_ns: + ns_str = str(namespace) + if self.from_ns or self.relative_count: if self.star: ns_str = '*' - return "from %s import %s" % (self.from_ns, ns_str) + '\n' + dots = '.' * self.relative_count + return "from %s%s import %s\n" % (dots, from_ns, ns_str) else: - return "import " + ns_str + '\n' + return "import %s\n" % ns_str def get_defined_names(self): if self.defunct: @@ -1533,7 +1534,11 @@ class PyFuzzyParser(object): relative_count += 1 # the from import mod, token_type, tok = self._parsedotname(self.current) - if not mod or tok != "import": + if str(mod) == 'import' and relative_count: + self.gen.push_back(self._current_full) + tok = 'import' + mod = None + if not mod and not relative_count or tok != "import": debug.warning("from: syntax error@%s" % self.start_pos[0]) defunct = True @@ -1550,6 +1555,7 @@ class PyFuzzyParser(object): i = Import(first_pos, self.end_pos, mod, defunct=True, relative_count=relative_count) self._check_user_stmt(i) + self.scope.add_import(i) self.freshscope = False #loops elif tok == 'for': diff --git a/test/completion/imports.py b/test/completion/imports.py index 6a0aebb4..ddca5d5b 100644 --- a/test/completion/imports.py +++ b/test/completion/imports.py @@ -132,3 +132,39 @@ from datetime.timedelta import #? ['Cursor'] from sqlite3 import Cursor + +# ----------------- +# relative imports +# ----------------- + +from .import_tree import mod1 +#? int() +mod1.a + +from ..import_tree import mod1 +#? +mod1.a + +from .......import_tree import mod1 +#? +mod1.a + +from .. import run +#? int() +run.tests_fail + +from ..run import tests_fail as f +#? int() +f + +from . import run +#? [] +run. + +from . import import_tree as imp_tree +#? str() +imp_tree.a + +from . import datetime +#? [] +mod1.