Import Management¶
Rejig provides comprehensive tools for managing Python imports: organizing, detecting unused imports, finding circular dependencies, and converting between relative and absolute imports.
Adding and Removing Imports¶
Add Imports¶
from rejig import Rejig
rj = Rejig("src/")
file = rj.file("module.py")
# Add a simple import
file.add_import("import os")
# Add a from import
file.add_import("from typing import Optional, List")
# Add with alias
file.add_import("import numpy as np")
file.add_import("from myapp.models import User")
The new import is inserted after the last existing import and the file's imports are then re-sorted with isort, so it ends up in the correct group regardless of where it was placed. See Sorting Imports to control or disable this.
Remove Imports¶
Imports are removed through targets. Find them with find_imports() or
find_unused_imports(), then call delete() on a target (or the list).
# Remove all unused imports in one step
file.remove_unused_imports()
# Or operate on targets
file.find_unused_imports().delete()
# Delete a single import target
imp = file.find_imports().filter_relative().to_list()[0]
imp.delete()
remove_unused_imports() also re-sorts the remaining imports by default (see
Sorting Imports).
Sorting Imports¶
Rejig sorts imports with isort, configured from
your project's own settings. isort discovers the nearest pyproject.toml,
setup.cfg, or .isort.cfg by searching upward from the file, so the result
matches exactly what your project's isort / pre-commit / CI run would produce —
including options such as profile, force_single_line, known_third_party,
and src_paths.
Automatic sorting¶
By default, the import-mutating operations re-sort a file's imports right after
they change it. This is why add_import does not need to guess the correct group
for a new import — it is inserted anywhere, and isort moves it to its section:
rj = Rejig("src/")
file = rj.file("module.py")
file.add_import("import os") # inserted, then imports re-sorted
file.remove_unused_imports() # unused removed, then imports re-sorted
Disable it globally on the Rejig instance, or per call with the sort
argument:
# Never auto-sort in this session
rj = Rejig("src/", auto_sort_imports=False)
# Auto-sort is on by default, but skip it for a single call
file.add_import("import os", sort=False)
file.remove_unused_imports(sort=False)
Manual sorting¶
Call sort_imports() to sort a file's imports on demand — for example after a
batch of edits made with auto_sort_imports=False:
sort_imports() reports success as a no-op when the imports are already sorted.
Finding Imports¶
Find All Imports¶
imports = file.find_imports()
for imp in imports:
print(f"{imp.line_number}: {imp.module}")
print(f" Names: {imp.names}")
print(f" Is relative: {imp.is_relative}")
print(f" Is unused: {imp.is_unused}")
Filter Imports¶
find_imports() takes no arguments and returns an ImportTargetList. Narrow
results with the built-in filters:
# Find relative imports
relative = file.find_imports().filter_relative()
# Find absolute imports
absolute = file.find_imports().filter_absolute()
# Find unused imports
unused = file.find_imports().filter_unused()
# Restrict to a specific file
in_file = file.find_imports().in_file("module.py")
Import Analysis¶
ImportTarget Properties¶
Each ImportTarget returned by find_imports() exposes:
imp = file.find_imports().to_list()[0]
imp.line_number # 5
imp.module # "typing" (None for "import os" style)
imp.names # ["Optional", "List"]
imp.is_relative # False
imp.is_unused # True if the import is never used
The full underlying ImportInfo (with extra detail) is available via
imp.import_info:
info = imp.import_info
info.import_statement # "from typing import Optional, List"
info.aliases # {"L": "List"} if aliased
info.is_from_import # True
info.relative_level # 0 (1 for ".", 2 for "..", etc.)
info.is_future # True for __future__ imports
info.is_type_checking # True if inside TYPE_CHECKING block
info.get_imported_names() # ["Optional", "List"]
info.get_original_name("L") # "List" (resolve alias)
Detect Unused Imports¶
from rejig import ImportAnalyzer
analyzer = ImportAnalyzer(rj)
# Find unused imports in a file (returns ImportInfo objects)
unused = analyzer.find_unused_imports(file.path)
for info in unused:
print(f"Unused: {info.import_statement} at line {info.line_number}")
# Remove all unused imports
file.remove_unused_imports()
# Or use the target-based batch operation
file.find_unused_imports().delete()
Detect Potentially Missing Imports¶
# Find names that are used but appear to have no definition or import
missing = analyzer.find_potentially_missing_imports(file.path)
for name in missing:
print(f"Possibly missing import for: {name}")
Import Organization¶
For most cases, prefer sort_imports(), which runs isort
itself with your project's configuration. ImportOrganizer is a built-in
alternative that organizes imports without an isort dependency on the call site:
it sorts imports into sections (future, standard library, third-party,
first-party, local) and orders them within each section.
from rejig import ImportOrganizer
organizer = ImportOrganizer(rj)
# Organize imports in a single file (takes a Path)
organizer.organize(file.path)
# Organize every Python file in the project
for f in rj.find_files():
organizer.organize(f.path)
First-Party Packages¶
First-party packages are auto-detected, but you can specify them explicitly when constructing the organizer:
organizer = ImportOrganizer(rj, first_party_packages={"mypackage"})
for f in rj.find_files():
organizer.organize(f.path)
Circular Import Detection¶
Circular imports can cause runtime errors. Rejig can detect them:
from rejig import ImportGraph
graph = ImportGraph(rj)
# Build the import graph
graph.build()
# Find all circular imports
cycles = graph.find_circular_imports()
for cycle in cycles:
print(f"Circular import chain:")
print(f" {' -> '.join(cycle.cycle)}")
# Or simply: print(str(cycle))
Import Graph Analysis¶
# Get all modules that import a specific module (direct dependents)
dependents = graph.get_dependents("myapp.models")
print(f"Modules importing myapp.models: {dependents}")
# Get all modules imported by a specific module (direct dependencies)
deps = graph.get_dependencies("myapp.views")
print(f"Modules imported by myapp.views: {deps}")
# Get all transitive dependencies of a module
all_deps = graph.get_all_dependencies("myapp.views")
print(f"All (transitive) dependencies: {all_deps}")
Relative/Absolute Conversion¶
Conversion is done per import target.
Convert to Relative Imports¶
# Convert absolute imports to relative within the same package
for imp in file.find_imports().filter_absolute():
imp.convert_to_relative()
# Example:
# Before: from myapp.models import User
# After: from .models import User
Convert to Absolute Imports¶
# Convert relative imports to absolute
for imp in file.find_imports().filter_relative():
imp.convert_to_absolute(package_name="myapp")
# Example:
# Before: from .models import User
# After: from myapp.models import User
Import Targets¶
Work with imports as targets for batch operations:
# Get import targets
imports = file.find_imports()
# Filter imports
relative = imports.filter_relative()
absolute = imports.filter_absolute()
unused = imports.filter_unused()
# Batch delete (e.g. remove all unused imports)
imports.filter_unused().delete()
# Inspect individual targets
for imp in imports:
print(imp.line_number, imp.module, imp.names)
Common Patterns¶
Clean Up Imports After Refactoring¶
rj = Rejig("src/")
# After removing code, clean up unused imports
for file in rj.find_files():
unused = file.find_unused_imports()
if unused:
print(f"Removing {len(unused)} unused imports from {file.path}")
unused.delete()
Modernize typing Imports¶
# Modernize type hints to Python 3.10+ syntax, then drop now-unused imports
for file in rj.find_files():
file.modernize_type_hints()
# Clean up imports that are no longer referenced
# (e.g. "from typing import List, Dict" once List/Dict are gone)
for file in rj.find_files():
file.find_unused_imports().delete()
Reorganize Imports¶
# Sort and group imports across the project
from rejig import ImportOrganizer
organizer = ImportOrganizer(rj)
for file in rj.find_files():
organizer.organize(file.path)
# Before:
# from typing import Optional
# import os
# from typing import List
# After:
# import os
#
# from typing import List, Optional
Integration with Other Features¶
With Circular Import Detection¶
# Find circular imports across the project
from rejig import ImportGraph
graph = ImportGraph(rj)
graph.build()
for cycle in graph.find_circular_imports():
print(f"Circular import: {cycle}")