--- ./logilab/common/modutils.py.orig 2007-12-21 14:57:52.000000000 +0100 +++ ./logilab/common/modutils.py 2008-02-06 18:42:22.209555200 +0100 @@ -227,6 +227,8 @@ :param modpath: splitted module's name (i.e name of a module or package splitted on '.') + (this means explicit relative imports that start with dots have + empty strings in this list!) :type path: list or None :param path: @@ -302,9 +304,20 @@ raise ImportError(dotted_name) return parts[0] # don't use += or insert, we want a new list to be created ! - for i in range(len(parts)): + path = None + starti = 0 + if parts[0] == '': + assert context_file is not None, \ + 'explicit relative import, but no context_file?' + path = [] # prevent resolving the import non-relatively + starti = 1 + while parts[starti] == '': # for all further dots: change context + starti += 1 + context_file = dirname(context_file) + for i in range(starti, len(parts)): try: - file_from_modpath(parts[:i+1], context_file=context_file) + file_from_modpath(parts[starti:i+1], + path=path, context_file=context_file) except ImportError: if not i >= max(1, len(parts) - 2): raise @@ -540,7 +553,7 @@ :type modpath: list or tuple :param modpath: splitted module's name (i.e name of a module or package splitted - on '.') + on '.'), with leading empty strings for explicit relative import :type path: list or None :param path: --- ./pylint/checkers/imports.py.orig 2008-02-06 15:57:32.669094400 +0100 +++ ./pylint/checkers/imports.py 2008-02-06 18:45:38.762184000 +0100 @@ -27,7 +27,7 @@ from pylint.checkers import BaseChecker, EmptyReport from pylint.checkers.utils import are_exclusive -def get_first_import(context, name, base): +def get_first_import(context, name, base, level=0): """return the node where [base.] is imported or None if not found """ for node in context.values(): @@ -35,7 +35,7 @@ if name in [iname[0] for iname in node.names]: return node if isinstance(node, astng.From): - if base == node.modname and \ + if base == node.modname and level == node.level and \ name in [iname[0] for iname in node.names]: return node @@ -242,7 +242,7 @@ def visit_from(self, node): - """triggered when an import statement is seen""" + """triggered when a from statement is seen""" basename = node.modname if basename == '__future__': # check this is the first non docstring statement in the module @@ -253,24 +253,27 @@ and prev.modname == '__future__'): self.add_message('W0410', node=node) self._check_deprecated(node, basename) - relative = self._check_relative(node, basename) + level = node.level + if level > 0: # explicit relative import (leading dots) + relative = True + else: + relative = self._check_relative(node, basename) for name, _ in node.names: if name == '*': self.add_message('W0401', args=basename, node=node) continue # handle reimport - self._check_reimport(node, name, basename) + self._check_reimport(node, name, basename, level) # analyze dependencies - fullname = '%s.%s' % (basename, name) - if fullname.find('.') > -1: - try: - # XXXFIXME: don't use get_module_part which doesn't take - # care of package precedence - fullname = get_module_part(fullname, - context_file=node.root().file) - except ImportError, ex: - self.add_message('F0401', args=(fullname, ex), node=node) - continue + fullname = '.' * level + '%s.%s' % (basename, name) + try: + # XXXFIXME: don't use get_module_part which doesn't take + # care of package precedence + fullname = get_module_part(fullname, + context_file=node.root().file) + except ImportError, ex: + self.add_message('F0401', args=(fullname, ex), node=node) + continue self._imported_module(node, fullname, relative) def _imported_module(self, node, mod_path, relative): @@ -278,8 +281,15 @@ """ context_name = node.root().name if relative: - mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), - mod_path) + context_parts = context_name.split('.') + if mod_path.startswith('.'): + while mod_path[0] == '.': + mod_path = mod_path[1:] + del context_parts[-1] # one level upwards + context_parts.append(mod_path) + else: + context_parts[-1] = mod_path + mod_path = '.'.join(context_parts) if context_name == mod_path: # module importing itself ! self.add_message('W0406', node=node) @@ -311,11 +321,11 @@ or mod_path[len(mod_name)] == '.'): self.add_message('W0402', node=node, args=mod_path) - def _check_reimport(self, node, name, basename=None): + def _check_reimport(self, node, name, basename=None, level=0): """check if the import is necessary (i.e. not already done) """ frame = node.frame() - first = get_first_import(frame, name, basename) + first = get_first_import(frame, name, basename, level) if isinstance(first, (astng.Import, astng.From)) and first is not node \ and not are_exclusive(first, node): self.add_message('W0404', node=node, args=(name, first.lineno))