forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Namespace implementation (python#1645)
- Loading branch information
Showing
11 changed files
with
443 additions
and
140 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import os | ||
|
||
from unittest import mock, TestCase | ||
from typing import List, Set | ||
|
||
from mypy.build import ModuleDiscovery, find_module_clear_caches | ||
from mypy.myunit import Suite, assert_equal | ||
|
||
|
||
class ModuleDiscoveryTestCase(Suite): | ||
def set_up(self) -> None: | ||
self.files = set() # type: Set[str] | ||
|
||
self._setup_mock_filesystem() | ||
|
||
def tear_down(self) -> None: | ||
self._teardown_mock_filesystem() | ||
find_module_clear_caches() | ||
|
||
def _list_dir(self, path: str) -> List[str]: | ||
res = set() | ||
|
||
if not path.endswith(os.path.sep): | ||
path = path + os.path.sep | ||
|
||
for item in self.files: | ||
if item.startswith(path): | ||
remnant = item.replace(path, '') | ||
segments = remnant.split(os.path.sep) | ||
if segments: | ||
res.add(segments[0]) | ||
|
||
return list(res) | ||
|
||
def _is_file(self, path: str) -> bool: | ||
return path in self.files | ||
|
||
def _is_dir(self, path: str) -> bool: | ||
for item in self.files: | ||
if not item.endswith('/'): | ||
item += '/' | ||
if item.startswith(path): | ||
return True | ||
return False | ||
|
||
def _setup_mock_filesystem(self) -> None: | ||
self._listdir_patcher = mock.patch('os.listdir', side_effect=self._list_dir) | ||
self._listdir_mock = self._listdir_patcher.start() | ||
self._isfile_patcher = mock.patch('os.path.isfile', side_effect=self._is_file) | ||
self._isfile_mock = self._isfile_patcher.start() | ||
self._isdir_patcher = mock.patch('os.path.isdir', side_effect=self._is_dir) | ||
self._isdir_mock = self._isdir_patcher.start() | ||
|
||
def _teardown_mock_filesystem(self) -> None: | ||
self._listdir_patcher.stop() | ||
self._isfile_patcher.stop() | ||
self._isdir_patcher.stop() | ||
|
||
def test_module_vs_package(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod.py'), | ||
os.path.join('dir2', 'mod', '__init__.py'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=False) | ||
path = m.find_module('mod') | ||
assert_equal(path, os.path.join('dir1', 'mod.py')) | ||
|
||
m = ModuleDiscovery(['dir2', 'dir1'], namespaces_allowed=False) | ||
path = m.find_module('mod') | ||
assert_equal(path, os.path.join('dir2', 'mod', '__init__.py')) | ||
|
||
def test_package_in_different_directories(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', '__init__.py'), | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', '__init__.py'), | ||
os.path.join('dir2', 'mod', 'b.py'), | ||
} | ||
m = ModuleDiscovery(['./dir1', './dir2'], namespaces_allowed=False) | ||
path = m.find_module('mod.a') | ||
assert_equal(path, os.path.join('dir1', 'mod', 'a.py')) | ||
|
||
path = m.find_module('mod.b') | ||
assert_equal(path, None) | ||
|
||
def test_package_with_stubs(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', '__init__.py'), | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', '__init__.pyi'), | ||
os.path.join('dir2', 'mod', 'b.pyi'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=False) | ||
path = m.find_module('mod.a') | ||
assert_equal(path, os.path.join('dir1', 'mod', 'a.py')) | ||
|
||
path = m.find_module('mod.b') | ||
assert_equal(path, os.path.join('dir2', 'mod', 'b.pyi')) | ||
|
||
def test_namespaces(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', 'b.py'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=True) | ||
path = m.find_module('mod.a') | ||
assert_equal(path, os.path.join('dir1', 'mod', 'a.py')) | ||
|
||
path = m.find_module('mod.b') | ||
assert_equal(path, os.path.join('dir2', 'mod', 'b.py')) | ||
|
||
def test_find_modules_recursive(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', '__init__.py'), | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', '__init__.pyi'), | ||
os.path.join('dir2', 'mod', 'b.pyi'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=True) | ||
srcs = m.find_modules_recursive('mod') | ||
assert_equal([s.module for s in srcs], ['mod', 'mod.a', 'mod.b']) | ||
|
||
def test_find_modules_recursive_with_namespace(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', 'b.py'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=True) | ||
srcs = m.find_modules_recursive('mod') | ||
assert_equal([s.module for s in srcs], ['mod.a', 'mod.b']) | ||
|
||
def test_find_modules_recursive_with_stubs(self) -> None: | ||
self.files = { | ||
os.path.join('dir1', 'mod', '__init__.py'), | ||
os.path.join('dir1', 'mod', 'a.py'), | ||
os.path.join('dir2', 'mod', '__init__.pyi'), | ||
os.path.join('dir2', 'mod', 'a.pyi'), | ||
} | ||
m = ModuleDiscovery(['dir1', 'dir2'], namespaces_allowed=True) | ||
srcs = m.find_modules_recursive('mod') | ||
assert_equal([s.module for s in srcs], ['mod', 'mod.a']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
-- Type checker test cases dealing with namespaces imports | ||
|
||
[case testAccessModuleInsideNamespace] | ||
# flags: --namespace-packages | ||
from ns import a | ||
[file ns/a.py] | ||
class A: pass | ||
def f(a: A) -> None: pass | ||
|
||
[case testAccessModuleInsideNamespaceNoNamespacePackages] | ||
from ns import a | ||
[file ns/a.py] | ||
class A: pass | ||
def f(a: A) -> None: pass | ||
[out] | ||
main:1: error: Cannot find module named 'ns' | ||
main:1: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) | ||
|
||
[case testAccessPackageInsideNamespace] | ||
# flags: --namespace-packages | ||
from ns import a | ||
[file ns/a/__init__.py] | ||
class A: pass | ||
def f(a: A) -> None: pass | ||
|
||
[case testAccessPackageInsideNamespaceLocatedInSeparateDirectories] | ||
# flags: --config-file tmp/mypy.ini | ||
from ns import a, b | ||
[file mypy.ini] | ||
[[mypy] | ||
namespace_packages = True | ||
mypy_path = ./tmp/dir1:./tmp/dir2 | ||
[file dir1/ns/a/__init__.py] | ||
class A: pass | ||
def f(a: A) -> None: pass | ||
[file dir2/ns/b.py] | ||
class B: pass | ||
def f(a: B) -> None: pass | ||
|
||
[case testConflictingPackageAndNamespaceFromImport] | ||
# flags: --config-file tmp/mypy.ini | ||
from pkg_or_ns import a | ||
from pkg_or_ns import b # E: Module 'pkg_or_ns' has no attribute 'b' | ||
[file mypy.ini] | ||
[[mypy] | ||
namespace_packages = True | ||
mypy_path = ./tmp/dir:./tmp/other_dir | ||
[file dir/pkg_or_ns/__init__.py] | ||
[file dir/pkg_or_ns/a.py] | ||
[file other_dir/pkg_or_ns/b.py] | ||
|
||
[case testConflictingPackageAndNamespaceImport] | ||
# flags: --config-file tmp/mypy.ini | ||
import pkg_or_ns.a | ||
import pkg_or_ns.b | ||
[file mypy.ini] | ||
[[mypy] | ||
namespace_packages = True | ||
mypy_path = ./tmp/dir:./tmp/other_dir | ||
[file dir/pkg_or_ns/__init__.py] | ||
[file dir/pkg_or_ns/a.py] | ||
[file other_dir/pkg_or_ns/b.py] | ||
[out] | ||
main:3: error: Cannot find module named 'pkg_or_ns.b' | ||
main:3: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) | ||
|
||
[case testConflictingModuleAndNamespace] | ||
# flags: --config-file tmp/mypy.ini | ||
from mod_or_ns import a | ||
from mod_or_ns import b # E: Module 'mod_or_ns' has no attribute 'b' | ||
[file mypy.ini] | ||
[[mypy] | ||
namespace_packages = True | ||
mypy_path = ./tmp/dir:./tmp/other_dir | ||
[file dir/mod_or_ns.py] | ||
a = None | ||
[file other_dir/mod_or_ns/b.py] | ||
|
||
[case testeNamespaceInsidePackage] | ||
# flags: --config-file tmp/mypy.ini | ||
from pkg.ns import a | ||
[file mypy.ini] | ||
[[mypy] | ||
namespace_packages = True | ||
[file pkg/__init__.py] | ||
[file pkg/ns/a.py] | ||
[out] |