Skip to content

Commit 0d9fa55

Browse files
committed
Adding relative import support (#60)
* Changing the `all_imported_modules_in_file(self, file)` projection in build which gathers imports from file ASTs root nodes. * Adding support for the relative import syntax in noderepr.py and output.py. * Adding a relative counter int to the AST nodes and fixing up treetransform.py. * Changing the `parse_import_from(self)` function to parse relative imports.
1 parent ed32d9a commit 0d9fa55

File tree

6 files changed

+42
-15
lines changed

6 files changed

+42
-15
lines changed

mypy/build.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import sys
1717
from os.path import dirname, basename
1818

19-
from typing import Undefined, Dict, List, Tuple, cast, Set
19+
from typing import Undefined, Dict, List, Tuple, cast, Set, Union
2020

2121
from mypy.types import Type
2222
from mypy.nodes import MypyFile, Node, Import, ImportFrom, ImportAll
@@ -451,21 +451,34 @@ def all_imported_modules_in_file(self,
451451
Return list of tuples (module id, import line number) for all modules
452452
imported in file.
453453
"""
454+
def correct_rel_imp(imp: Union[ImportFrom, ImportAll]) -> str:
455+
"""Function to correct for relative imports."""
456+
file_id = file.fullname()
457+
rel = imp.relative
458+
if rel == 0:
459+
return imp.id
460+
if os.path.basename(file.path) == '__init__.py':
461+
rel -= 1
462+
if rel != 0:
463+
file_id = ".".join(file_id.split(".")[:-rel])
464+
return file_id + "." + imp.id if imp.id else file_id
465+
454466
res = List[Tuple[str, int]]()
455467
for imp in file.imports:
456468
if not imp.is_unreachable:
457469
if isinstance(imp, Import):
458470
for id, _ in imp.ids:
459471
res.append((id, imp.line))
460472
elif isinstance(imp, ImportFrom):
461-
res.append((imp.id, imp.line))
473+
cur_id = correct_rel_imp(imp)
474+
res.append((cur_id, imp.line))
462475
# Also add any imported names that are submodules.
463476
for name, __ in imp.names:
464-
sub_id = imp.id + '.' + name
477+
sub_id = cur_id + '.' + name
465478
if self.is_module(sub_id):
466479
res.append((sub_id, imp.line))
467480
elif isinstance(imp, ImportAll):
468-
res.append((imp.id, imp.line))
481+
res.append((correct_rel_imp(imp), imp.line))
469482
return res
470483

471484
def is_module(self, id: str) -> bool:

mypy/noderepr.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,18 @@ def __init__(self, import_tok: Any, components: List[List[Token]],
3434
class ImportFromRepr:
3535
def __init__(self,
3636
from_tok: Any,
37+
rel_toks: Any,
3738
components: List[Token],
3839
import_tok: Any,
3940
lparen: Any,
4041
names: List[Tuple[List[Token], Token]],
4142
rparen: Any, br: Any) -> None:
4243
# Notes:
43-
# - lparen and rparen may be empty
44+
# - lparen, rparen, and rel_tok may be empty
4445
# - in each names tuple, the first item contains tokens for
4546
# 'name [as name]' and the second item is a comma or empty.
4647
self.from_tok = from_tok
48+
self.rel_toks = rel_toks
4749
self.components = components
4850
self.import_tok = import_tok
4951
self.lparen = lparen

mypy/nodes.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,22 @@ class ImportFrom(ImportBase):
163163

164164
names = Undefined(List[Tuple[str, str]]) # Tuples (name, as name)
165165

166-
def __init__(self, id: str, names: List[Tuple[str, str]]) -> None:
166+
def __init__(self, id: str, relative: int, names: List[Tuple[str, str]]) -> None:
167167
self.id = id
168168
self.names = names
169-
169+
self.relative = relative
170+
170171
def accept(self, visitor: NodeVisitor[T]) -> T:
171172
return visitor.visit_import_from(self)
172173

173174

174175
class ImportAll(ImportBase):
175176
"""from m import *"""
176177

177-
def __init__(self, id: str) -> None:
178+
def __init__(self, id: str, relative: int) -> None:
178179
self.id = id
179-
180+
self.relative = relative
181+
180182
def accept(self, visitor: NodeVisitor[T]) -> T:
181183
return visitor.visit_import_all(self)
182184

mypy/output.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def visit_import_all(self, o):
5555
def output_import_from_or_all(self, o):
5656
r = o.repr
5757
self.token(r.from_tok)
58+
self.tokens(r.rel_toks)
5859
self.tokens(r.components)
5960
self.token(r.import_tok)
6061
self.token(r.lparen)

mypy/parse.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,16 @@ def parse_import(self) -> Import:
170170

171171
def parse_import_from(self) -> Node:
172172
from_tok = self.expect('from')
173-
name, components = self.parse_qualified_name()
173+
relative = 0
174+
rel_toks = List[Token]()
175+
while self.current_str() == ".":
176+
rel_toks.append(self.expect('.'))
177+
relative += 1
178+
if self.current_str() == "import":
179+
name = ""
180+
components = List[Token]()
181+
else:
182+
name, components = self.parse_qualified_name()
174183
if name == self.custom_typing_module:
175184
name = 'typing'
176185
import_tok = self.expect('import')
@@ -180,7 +189,7 @@ def parse_import_from(self) -> Node:
180189
node = None # type: ImportBase
181190
if self.current_str() == '*':
182191
name_toks.append(([self.skip()], none))
183-
node = ImportAll(name)
192+
node = ImportAll(name, relative)
184193
else:
185194
is_paren = self.current_str() == '('
186195
if is_paren:
@@ -206,12 +215,12 @@ def parse_import_from(self) -> Node:
206215
if is_paren:
207216
rparen = self.expect(')')
208217
if node is None:
209-
node = ImportFrom(name, targets)
218+
node = ImportFrom(name, relative, targets)
210219
br = self.expect_break()
211220
self.imports.append(node)
212221
# TODO: Fix representation if there is a custom typing module import.
213222
self.set_repr(node, noderepr.ImportFromRepr(
214-
from_tok, components, import_tok, lparen, name_toks, rparen, br))
223+
from_tok, rel_toks, components, import_tok, lparen, name_toks, rparen, br))
215224
if name == '__future__':
216225
self.future_options.extend(target[0] for target in targets)
217226
return node

mypy/treetransform.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ def visit_import(self, node: Import) -> Node:
6060
return Import(node.ids[:])
6161

6262
def visit_import_from(self, node: ImportFrom) -> Node:
63-
return ImportFrom(node.id, node.names[:])
63+
return ImportFrom(node.id, node.relative, node.names[:])
6464

6565
def visit_import_all(self, node: ImportAll) -> Node:
66-
return ImportAll(node.id)
66+
return ImportAll(node.id, node.relative)
6767

6868
def visit_func_def(self, node: FuncDef) -> FuncDef:
6969
# Note that a FuncDef must be transformed to a FuncDef.

0 commit comments

Comments
 (0)