Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# **Upcoming release**

- Honor native module and namespace precedence when resolving imports: regular packages and modules win over namespace-package directories, and namespace portions are combined across source folders
- #895 patchedast Starred and keyword now consumes their syntactically expected and ** (@lieryan)
- #896 patchedast cleanup and refactoring (@lieryan)

Expand Down
72 changes: 38 additions & 34 deletions rope/base/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
import warnings
from contextlib import ExitStack
from typing import Optional
from typing import Optional, Union

import rope.base.fscommands # Use full qualification for clarity.
import rope.base.resourceobserver as resourceobserver
Expand Down Expand Up @@ -142,32 +142,27 @@ def get_relative_module(self, name, folder, level):
raise ModuleNotFoundError("Module %s not found" % name)
return self.pycore.resource_to_pyobject(module)

def find_module(self, modname, folder=None) -> Optional[File]:
def find_module(self, modname, folder=None) -> Optional[Union[File, Folder]]:
"""Returns a resource corresponding to the given module

returns None if it can not be found
"""
for src in self.get_source_folders():
module = _find_module_in_folder(src, modname)
if module is not None:
return module
for src in self.get_python_path_folders():
module = _find_module_in_folder(src, modname)
if module is not None:
return module
if folder is not None:
module = _find_module_in_folder(folder, modname)
if module is not None:
return module
return None

def folders():
yield from self.get_source_folders()
yield from self.get_python_path_folders()
if folder is not None:
yield folder

return _find_module_in_folders(folders(), modname)

def find_relative_module(self, modname, folder, level):
for i in range(level - 1):
folder = folder.parent
if modname == "":
return folder
else:
return _find_module_in_folder(folder, modname)
return _find_module_in_folders([folder], modname)

def is_ignored(self, resource):
return False
Expand Down Expand Up @@ -437,22 +432,31 @@ def _realpath(path):
return os.path.realpath(os.path.abspath(os.path.expanduser(path)))


def _find_module_in_folder(folder, modname):
module = folder
packages = modname.split(".")
for pkg in packages[:-1]:
if module.is_folder() and module.has_child(pkg):
module = module.get_child(pkg)
def _find_module_in_folders(folders, modname):
for name in modname.split("."):
namespaces = []
for folder in folders:
if not folder.is_folder():
continue
package = folder.get_child(name) if folder.has_child(name) else None
if package is not None and package.is_folder():
if (
package.has_child("__init__.py")
and not package.get_child("__init__.py").is_folder()
):
module = package
folders = [package]
break
namespaces.append(package)
if folder.has_child(name + ".py"):
source = folder.get_child(name + ".py")
if not source.is_folder():
module = source
folders = []
break
else:
return None
if module.is_folder():
if (
module.has_child(packages[-1])
and module.get_child(packages[-1]).is_folder()
):
return module.get_child(packages[-1])
elif (
module.has_child(packages[-1] + ".py")
and not module.get_child(packages[-1] + ".py").is_folder()
):
return module.get_child(packages[-1] + ".py")
if not namespaces:
return None
module = namespaces[0]
folders = namespaces
return module
76 changes: 76 additions & 0 deletions ropetest/pycoretest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from rope.base import exceptions, libutils
from rope.base.builtins import BuiltinClass, File
from rope.base.project import Project
from rope.base.pycore import _TextChangeDetector
from rope.base.pynamesdef import AssignedName
from rope.base.pyobjects import AbstractFunction, get_base_type
Expand Down Expand Up @@ -330,6 +331,81 @@ def test_find_module_when_module_and_package_with_the_same_name(self):
found_module = self.project.find_module("sample")
self.assertEqual(samplepkg, found_module)

def test_find_module_prefers_module_to_namespace_directory(self):
self.project.root.create_folder("sample")
samplemod = testutils.create_module(self.project, "sample")
self.assertEqual(samplemod, self.project.find_module("sample"))

def test_find_module_retains_namespace_directory(self):
namespace = self.project.root.create_folder("sample")
self.project.close()
self.project = Project(self.project.address, source_folders=[""])
self.assertEqual(namespace, self.project.find_module("sample"))

def test_find_nested_module_does_not_search_shadowed_namespace(self):
namespace = self.project.root.create_folder("sample")
testutils.create_module(self.project, "child", namespace)
testutils.create_module(self.project, "sample")
self.assertIsNone(self.project.find_module("sample.child"))

def test_find_module_defers_namespace_to_later_source_module(self):
first = self.project.root.create_folder("first")
second = self.project.root.create_folder("second")
first.create_folder("sample")
samplemod = testutils.create_module(self.project, "sample", second)
self.project.close()
self.project = Project(self.project.address, source_folders=["first", "second"])
self.assertEqual(samplemod, self.project.find_module("sample"))

def test_find_module_defers_namespace_to_later_regular_package(self):
first = self.project.root.create_folder("first")
second = self.project.root.create_folder("second")
namespace = first.create_folder("sample")
testutils.create_module(self.project, "child", namespace)
package = testutils.create_package(self.project, "sample", second)
self.project.close()
self.project = Project(self.project.address, source_folders=["first", "second"])
self.assertEqual(package, self.project.find_module("sample"))
self.assertIsNone(self.project.find_module("sample.child"))

def test_find_nested_module_searches_namespace_portions(self):
first = self.project.root.create_folder("first")
second = self.project.root.create_folder("second")
first.create_folder("sample")
namespace = second.create_folder("sample")
samplemod = testutils.create_module(self.project, "child", namespace)
self.project.close()
self.project = Project(self.project.address, source_folders=["first", "second"])
self.assertEqual(samplemod, self.project.find_module("sample.child"))

def test_relative_import_prefers_config_module_to_namespace_directory(self):
package = testutils.create_package(self.project, "sample")
package.create_folder("config")
config = testutils.create_module(self.project, "config", package)
config.write("class FlextCliConfig:\n pass\n")
consumer = testutils.create_module(self.project, "consumer", package)
consumer.write("from .config import FlextCliConfig\n")
expected = self.project.get_pymodule(config)["FlextCliConfig"].get_object()
actual = self.project.get_pymodule(consumer)["FlextCliConfig"].get_object()
self.assertEqual(expected, actual)

def test_relative_import_keeps_regular_package_precedence(self):
package = testutils.create_package(self.project, "sample")
testutils.create_module(self.project, "config", package)
config = testutils.create_package(self.project, "config", package)
config.get_child("__init__.py").write("class Configuration:\n pass\n")
consumer = testutils.create_module(self.project, "consumer", package)
consumer.write("from .config import Configuration\n")
expected = self.project.get_pymodule(config)["Configuration"].get_object()
actual = self.project.get_pymodule(consumer)["Configuration"].get_object()
self.assertEqual(expected, actual)

def test_relative_module_does_not_search_unrelated_source_roots(self):
package = testutils.create_package(self.project, "sample")
testutils.create_module(self.project, "config")
with self.assertRaises(exceptions.ModuleNotFoundError):
self.project.get_relative_module("config", package, 1)

def test_source_folders_preference(self):
testutils.create_package(self.project, "pkg1")
testutils.create_package(self.project, "pkg1.src2")
Expand Down
Loading