"""A package for handling imports
This package provides tools for modifying module imports after
refactorings or as a separate task.
"""
import rope.base.evaluate
from rope.base.change import ChangeSet,ChangeContents
from rope.refactor import occurrences,rename
from rope.refactor.importutils import module_imports,actions
from rope.refactor.importutils.importinfo import NormalImport,FromImport
import rope.base.codeanalyze
class ImportOrganizer(object):
"""Perform some import-related commands
Each method returns a `rope.base.change.Change` object.
"""
def __init__(self, project):
self.project = project
self.pycore = project.pycore
self.import_tools = ImportTools(self.pycore)
def organize_imports(self, resource, offset=None):
return self._perform_command_on_import_tools(
self.import_tools.organize_imports, resource, offset)
def expand_star_imports(self, resource, offset=None):
return self._perform_command_on_import_tools(
self.import_tools.expand_stars, resource, offset)
def froms_to_imports(self, resource, offset=None):
return self._perform_command_on_import_tools(
self.import_tools.froms_to_imports, resource, offset)
def relatives_to_absolutes(self, resource, offset=None):
return self._perform_command_on_import_tools(
self.import_tools.relatives_to_absolutes, resource, offset)
def handle_long_imports(self, resource, offset=None):
return self._perform_command_on_import_tools(
self.import_tools.handle_long_imports, resource, offset)
def _perform_command_on_import_tools(self, method, resource, offset):
pymodule = self.pycore.resource_to_pyobject(resource)
before_performing = pymodule.source_code
import_filter = None
if offset is not None:
import_filter = self._line_filter(
pymodule.lines.get_line_number(offset))
result = method(pymodule, import_filter=import_filter)
if result is not None and result != before_performing:
changes = ChangeSet(method.__name__.replace('_', ' ') +
' in <%s>' % resource.path)
changes.add_change(ChangeContents(resource, result))
return changes
def _line_filter(self, lineno):
def import_filter(import_stmt):
return import_stmt.start_line <= lineno < import_stmt.end_line
return import_filter
class ImportTools(object):
def __init__(self, pycore):
self.pycore = pycore
def get_import(self, resource):
"""The import statement for `resource`"""
module_name = self.pycore.modname(resource)
return NormalImport(((module_name, None), ))
def get_from_import(self, resource, name):
"""The from import statement for `name` in `resource`"""
module_name = self.pycore.modname(resource)
names = []
if isinstance(name, list):
names = [(imported, None) for imported in name]
else:
names = [(name, None),]
return FromImport(module_name, 0, tuple(names))
def module_imports(self, module, imports_filter=None):
return module_imports.ModuleImports(self.pycore, module,
imports_filter)
def froms_to_imports(self, pymodule, import_filter=None):
pymodule = self._clean_up_imports(pymodule, import_filter)
module_imports = self.module_imports(pymodule, import_filter)
for import_stmt in module_imports.imports:
if import_stmt.readonly or \
not self._is_transformable_to_normal(import_stmt.import_info):
continue
pymodule = self._from_to_normal(pymodule, import_stmt)
# Adding normal imports in place of froms
module_imports = self.module_imports(pymodule, import_filter)
for import_stmt in module_imports.imports:
if not import_stmt.readonly and \
self._is_transformable_to_normal(import_stmt.import_info):
import_stmt.import_info = \
NormalImport(((import_stmt.import_info.module_name, None),))
module_imports.remove_duplicates()
return module_imports.get_changed_source()
def expand_stars(self, pymodule, import_filter=None):
module_imports = self.module_imports(pymodule, import_filter)
module_imports.expand_stars()
return module_imports.get_changed_source()
def _from_to_normal(self, pymodule, import_stmt):
resource = pymodule.get_resource()
from_import = import_stmt.import_info
module_name = from_import.module_name
for name, alias in from_import.names_and_aliases:
imported = name
if alias is not None:
imported = alias
occurrence_finder = occurrences.create_finder(
self.pycore, imported, pymodule[imported], imports=False)
source = rename.rename_in_module(
occurrence_finder, module_name + '.' + name,
pymodule=pymodule, replace_primary=True)
if source is not None:
pymodule = self.pycore.get_string_module(source, resource)
return pymodule
def _clean_up_imports(self, pymodule, import_filter):
resource = pymodule.get_resource()
module_with_imports = self.module_imports(pymodule, import_filter)
module_with_imports.expand_stars()
source = module_with_imports.get_changed_source()
if source is not None:
pymodule = self.pycore.get_string_module(source, resource)
source = self.relatives_to_absolutes(pymodule)
if source is not None:
pymodule = self.pycore.get_string_module(source, resource)
module_with_imports = self.module_imports(pymodule, import_filter)
module_with_imports.remove_duplicates()
module_with_imports.remove_unused_imports()
source = module_with_imports.get_changed_source()
if source is not None:
pymodule = self.pycore.get_string_module(source, resource)
return pymodule
def relatives_to_absolutes(self, pymodule, import_filter=None):
module_imports = self.module_imports(pymodule, import_filter)
to_be_absolute_list = module_imports.get_relative_to_absolute_list()
for name, absolute_name in to_be_absolute_list:
pymodule = self._rename_in_module(pymodule, name, absolute_name)
module_imports = self.module_imports(pymodule, import_filter)
module_imports.get_relative_to_absolute_list()
source = module_imports.get_changed_source()
if source is None:
source = pymodule.source_code
return source
def _is_transformable_to_normal(self, import_info):
if not isinstance(import_info, FromImport):
return False
return True
def organize_imports(self, pymodule,
unused=True, duplicates=True,
selfs=True, sort=True, import_filter=None):
if unused or duplicates:
module_imports = self.module_imports(pymodule, import_filter)
if unused:
module_imports.remove_unused_imports()
if duplicates:
module_imports.remove_duplicates()
source = module_imports.get_changed_source()
if source is not None:
pymodule = self.pycore.get_string_module(
source, pymodule.get_resource())
if selfs:
pymodule = self._remove_self_imports(pymodule, import_filter)
if sort:
return self.sort_imports(pymodule, import_filter)
else:
return pymodule.source_code
def _remove_self_imports(self, pymodule, import_filter=None):
module_imports = self.module_imports(pymodule, import_filter)
to_be_fixed, to_be_renamed = module_imports.get_self_import_fix_and_rename_list()
for name in to_be_fixed:
try:
pymodule = self._rename_in_module(pymodule, name, '', till_dot=True)
except ValueError:
# There is a self import with direct access to it
return pymodule
for name, new_name in to_be_renamed:
pymodule = self._rename_in_module(pymodule, name, new_name)
module_imports = self.module_imports(pymodule, import_filter)
module_imports.get_self_import_fix_and_rename_list()
source = module_imports.get_changed_source()
if source is not None:
pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
return pymodule
def _rename_in_module(self, pymodule, name, new_name, till_dot=False):
old_name = name.split('.')[-1]
old_pyname = rope.base.evaluate.eval_str(pymodule.get_scope(), name)
occurrence_finder = occurrences.create_finder(
self.pycore, old_name, old_pyname, imports=False)
changes = rope.base.codeanalyze.ChangeCollector(pymodule.source_code)
for occurrence in occurrence_finder.find_occurrences(pymodule=pymodule):
start, end = occurrence.get_primary_range()
if till_dot:
new_end = pymodule.source_code.index('.', end) + 1
space = pymodule.source_code[end:new_end - 1].strip()
if not space == '':
for c in space:
if not c.isspace() and c not in '\\':
raise ValueError()
end = new_end
changes.add_change(start, end, new_name)
source = changes.get_changed()
if source is not None:
pymodule = self.pycore.get_string_module(source, pymodule.get_resource())
return pymodule
def sort_imports(self, pymodule, import_filter=None):
module_imports = self.module_imports(pymodule, import_filter)
module_imports.sort_imports()
return module_imports.get_changed_source()
def handle_long_imports(self, pymodule, maxdots=2, maxlength=27,
import_filter=None):
# IDEA: `maxdots` and `maxlength` can be specified in project config
# adding new from imports
module_imports = self.module_imports(pymodule, import_filter)
to_be_fixed = module_imports.handle_long_imports(maxdots, maxlength)
# performing the renaming
pymodule = self.pycore.get_string_module(
module_imports.get_changed_source(),
resource=pymodule.get_resource())
for name in to_be_fixed:
pymodule = self._rename_in_module(pymodule, name,
name.split('.')[-1])
# organizing imports
return self.organize_imports(pymodule, selfs=False, sort=False,
import_filter=import_filter)
def get_imports(pycore, pydefined):
"""A shortcut for getting the `ImportInfo`\s used in a scope"""
pymodule = pydefined.get_module()
module = module_imports.ModuleImports(pycore, pymodule)
if pymodule == pydefined:
return [stmt.import_info for stmt in module.imports]
return module.get_used_imports(pydefined)
def get_module_imports(pycore, pymodule):
"""A shortcut for creating a `module_imports.ModuleImports` object"""
return module_imports.ModuleImports(pycore, pymodule)
def add_import(pycore, pymodule, module_name, name=None):
imports = get_module_imports(pycore, pymodule)
candidates = []
names = []
# from mod import name
if name is not None:
from_import = FromImport(module_name, 0, [(name, None)])
names.append(name)
candidates.append(from_import)
# from pkg import mod
if '.' in module_name:
pkg, mod = module_name.rsplit('.', 1)
candidates.append(FromImport(pkg, 0, [(mod, None)]))
if name:
names.append(mod + '.' + name)
else:
names.append(mod)
# import mod
normal_import = NormalImport([(module_name, None)])
if name:
names.append(module_name + '.' + name)
else:
names.append(module_name)
candidates.append(normal_import)
visitor = actions.AddingVisitor(pycore, candidates)
selected_import = normal_import
for import_statement in imports.imports:
if import_statement.accept(visitor):
selected_import = visitor.import_info
break
imports.add_import(selected_import)
imported_name = names[candidates.index(selected_import)]
return imports.get_changed_source(), imported_name
|