# HG changeset patch # User Gregory Szorc # Date 2015-06-28 16:36:58 # Node ID 1f88c0f6ff5a7be68e8373e3b333ac240fbf4733 # Parent 0fca47b206f655b9632fa751e3d690a0285304d3 import-checker: resolve relative imports "from . import X" will produce an ImportFrom ast node with .module = None. This resulted in a run-time error from attempting to concatenate None with a str. Another problem with relative imports is that the prefix may be dynamic based on the "level" attribute of the import. e.g. "from ." has level 1 and "from .." has level 2. We teach the "fromlocal" function how to cope with relative imports. Where appropriate, the consumer passes in the level so relative module names may be resolved properly. diff --git a/contrib/import-checker.py b/contrib/import-checker.py --- a/contrib/import-checker.py +++ b/contrib/import-checker.py @@ -78,13 +78,26 @@ def fromlocalfunc(modulename, localmods) >>> # unknown = maybe standard library >>> fromlocal('os') False + >>> fromlocal(None, 1) + ('foo', 'foo.__init__', True) + >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods) + >>> fromlocal2(None, 2) + ('foo', 'foo.__init__', True) """ prefix = '.'.join(modulename.split('.')[:-1]) if prefix: prefix += '.' - def fromlocal(name): - # check relative name at first - for n in prefix + name, name: + def fromlocal(name, level=0): + # name is None when relative imports are used. + if name is None: + # If relative imports are used, level must not be absolute. + assert level > 0 + candidates = ['.'.join(modulename.split('.')[:-level])] + else: + # Check relative name first. + candidates = [prefix + name, name] + + for n in candidates: if n in localmods: return (n, n, False) dottedpath = n + '.__init__' @@ -239,7 +252,7 @@ def imported_modules(source, modulename, continue yield found[1] elif isinstance(node, ast.ImportFrom): - found = fromlocal(node.module) + found = fromlocal(node.module, node.level) if not found: # this should import standard library continue