diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 1d7c4937..8eb20670 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -343,6 +343,25 @@ class _Importer(object): @memoize_default(NO_DEFAULT) def follow_file_system(self): + # Handle "magic" Flask extension imports: + # ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``. + if [part._string for part in self.import_path[:2]] == ['flask', 'ext']: + orig_path = tuple(self.import_path) + part = orig_path[2] + pos = (part._line, part._column) + try: + self.import_path = ( + pr.NamePart('flask_' + part._string, part.parent, pos), + ) + orig_path[3:] + return self._real_follow_file_system() + except ModuleNotFound as e: + self.import_path = ( + pr.NamePart('flaskext', part.parent, pos), + ) + orig_path[2:] + return self._real_follow_file_system() + return self._real_follow_file_system() + + def _real_follow_file_system(self): if self.file_path: sys_path_mod = list(self.sys_path_with_modifications()) if not self.module.has_explicit_absolute_import: diff --git a/test/test_evaluate/flask-site-packages/flask_baz/__init__.py b/test/test_evaluate/flask-site-packages/flask_baz/__init__.py new file mode 100644 index 00000000..e9b3fffe --- /dev/null +++ b/test/test_evaluate/flask-site-packages/flask_baz/__init__.py @@ -0,0 +1 @@ +Baz = 1 diff --git a/test/test_evaluate/flask-site-packages/flask_foo.py b/test/test_evaluate/flask-site-packages/flask_foo.py new file mode 100644 index 00000000..0b910b80 --- /dev/null +++ b/test/test_evaluate/flask-site-packages/flask_foo.py @@ -0,0 +1,2 @@ +class Foo(object): + pass diff --git a/test/test_evaluate/flask-site-packages/flaskext/__init__.py b/test/test_evaluate/flask-site-packages/flaskext/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test_evaluate/flask-site-packages/flaskext/bar.py b/test/test_evaluate/flask-site-packages/flaskext/bar.py new file mode 100644 index 00000000..6629f9ae --- /dev/null +++ b/test/test_evaluate/flask-site-packages/flaskext/bar.py @@ -0,0 +1,2 @@ +class Bar(object): + pass diff --git a/test/test_evaluate/flask-site-packages/flaskext/moo/__init__.py b/test/test_evaluate/flask-site-packages/flaskext/moo/__init__.py new file mode 100644 index 00000000..266e8093 --- /dev/null +++ b/test/test_evaluate/flask-site-packages/flaskext/moo/__init__.py @@ -0,0 +1 @@ +Moo = 1 diff --git a/test/test_evaluate/test_imports.py b/test/test_evaluate/test_imports.py index 89e71570..4f929448 100644 --- a/test/test_evaluate/test_imports.py +++ b/test/test_evaluate/test_imports.py @@ -1,3 +1,6 @@ +import os +import sys + import pytest import jedi @@ -23,3 +26,36 @@ def test_import_not_in_sys_path(): assert a[0].name == 'str' a = jedi.Script(path='module.py', line=7).goto_definitions() assert a[0].name == 'str' + + +def setup_function(function): + sys.path.append(os.path.join( + os.path.dirname(__file__), 'flask-site-packages')) + + +def teardown_function(function): + path = os.path.join(os.path.dirname(__file__), 'flask-site-packages') + sys.path.remove(path) + + +@pytest.mark.parametrize("script,name", [ + ("from flask.ext import foo; foo.", "Foo"), # flask_foo.py + ("from flask.ext import bar; bar.", "Bar"), # flaskext/bar.py + ("from flask.ext import baz; baz.", "Baz"), # flask_baz/__init__.py + ("from flask.ext import moo; moo.", "Moo"), # flaskext/moo/__init__.py +]) +def test_flask_ext(script, name): + """flask.ext.foo is really imported from flaskext.foo or flask_foo. + """ + assert name in [c.name for c in jedi.Script(script).completions()] + + +@pytest.mark.xfail +@pytest.mark.parametrize("script,name", [ + ("import flask.ext.foo; flask.ext.foo.", "Foo"), + ("import flask.ext.bar; flask.ext.bar.", "Foo"), + ("import flask.ext.baz; flask.ext.baz.", "Foo"), + ("import flask.ext.moo; flask.ext.moo.", "Foo"), +]) +def test_flask_ext_more(script, name): + assert name in [c.name for c in jedi.Script(script).completions()]