import-analyzer-py
[!WARNING] This project is in early development and will undergo significant changes. The API is not stable and may change without notice.
A Python import analyzer with cross-file analysis, unused import detection, circular import warnings, and autofix.
Comparison with Other Tools
| Feature | This tool | Ruff | Autoflake | Pyflakes | Pylint | Unimport |
|---|---|---|---|---|---|---|
| Detect unused imports | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Autofix | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Cross-file analysis | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Re-export tracking | ✅ | ❌¹ | ❌ | ❌ | ❌² | ❌ |
| Cascade detection | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Circular import warnings | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Unreachable file warnings | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Indirect import fix | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
Respects __all__ |
✅ | ✅ | ⚠️³ | ✅ | ✅ | ✅ |
| noqa: F401 support | ✅ | ✅ | ✅ | ❌⁴ | ✅⁵ | ✅ |
| Full scope analysis (LEGB) | ✅ | ✅ | ⚠️⁶ | ⚠️⁶ | ✅ | ✅ |
| String annotations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| TYPE_CHECKING blocks | ✅ | ✅ | ✅ | ⚠️⁷ | ✅ | ✅ |
Type comments (# type:) |
❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
Redundant alias (x as x) |
❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Star import suggestions | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Redefinition warnings | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Speed | Moderate | 🚀 Fast | Moderate | Fast | Slow | Moderate |
¹ Ruff suggests import X as X for __init__.py but doesn't track actual cross-file usage
² Pylint skips __init__.py by default but doesn't track actual re-export consumers
³ Autoflake only uses __all__ to skip star import expansion, not to preserve re-exports
⁴ Pyflakes has no noqa support; Flake8 adds it as a wrapper layer
⁵ Pylint uses # pylint: disable=unused-import
⁶ Autoflake/Pyflakes have basic scope analysis but miss some LEGB edge cases
⁷ Pyflakes treats TYPE_CHECKING as normal conditional code (works but not explicit)
What makes this tool unique
Cross-file analysis — This is the only tool that follows imports from your entry point and tracks which imports are actually used by other files. This enables:
- Re-export preservation: If
utils.pyimportsListandmain.pydoesfrom utils import List, the import inutils.pyis correctly identified as used - Cascade detection: When removing an unused import makes another import unused, this tool finds all of them in a single pass (note: imports in
__all__are always preserved as public API) - Indirect import fix: When you import a name from a re-exporter instead of its original source, this tool can rewrite the import to use the direct source
- Circular import detection: Warns about import cycles in your codebase
- Unreachable file detection: Identifies files that become dead code after fixing imports
Features we don't have (yet)
Based on analysis of other tools' source code:
- Type comments:
# type: intstyle annotations (PEP 484) are not parsed - Redundant alias detection:
import X as Xas explicit re-export marker (Ruff feature) - Star import suggestions: Suggesting specific names to replace
from x import *(Unimport feature) - Redefinition warnings: Warning when an import is reassigned before use (Pyflakes feature)
Installation
pip install import-analyzer-py
Or install from source:
git clone https://github.com/cmyui/import-analyzer-py
cd import-analyzer-py
pip install .
pre-commit
Add to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/cmyui/import-analyzer-py
rev: v0.1.4 # or latest version
hooks:
# Cross-file analysis (recommended) - analyzes entire project
- id: import-analyzer
# Or for faster single-file analysis on changed files only:
# - id: import-analyzer-single-file
The cross-file hook runs with all features enabled by default:
--fix-unused-imports --fix-indirect-imports --warn-implicit-reexports --warn-circular --warn-unreachable
To customize:
hooks:
- id: import-analyzer
args: [.] # check only, no warnings
- id: import-analyzer
args: [., --fix-unused-imports] # fix but no warnings
Usage
Cross-file mode (default)
Cross-file mode follows imports from an entry point and tracks re-exports across your codebase. This prevents false positives when imports are used by other files.
# Analyze from entry point (follows imports)
import-analyzer main.py
# Analyze entire directory
import-analyzer src/
# Fix all unused imports (including cascaded ones)
import-analyzer --fix-unused-imports main.py
# Fix indirect imports (rewrite to use direct sources)
import-analyzer --fix-indirect-imports main.py
# Fix indirect imports including same-package re-exports (stricter)
import-analyzer --fix-indirect-imports --strict-indirect-imports main.py
# Warn about implicit re-exports (imports used by other files but not in __all__)
import-analyzer --warn-implicit-reexports main.py
# Warn about circular imports
import-analyzer --warn-circular main.py
# Warn about files that become unreachable after fixing
import-analyzer --warn-unreachable main.py
# Quiet mode (summary only)
import-analyzer -q main.py
Single-file mode
For simple use cases or when you want to analyze files independently:
# Check files independently (no cross-file tracking)
import-analyzer --single-file myfile.py
# Check multiple files
import-analyzer --single-file src/*.py
Exit codes
| Code | Meaning |
|---|---|
| 0 | No unused imports found (or --fix-unused-imports was used) |
| 1 | Unused imports found |
Features
Cross-file analysis
- Re-export tracking: Imports used by other files are preserved
- Cascade detection: Finds all unused imports in a single pass, even when removing one exposes another
- Indirect import fix: Rewrites imports that go through re-exporters to use the original source directly
- Circular import detection: Warns about import cycles
- Implicit re-export warnings: Identifies re-exports missing from
__all__ - Unreachable file detection: Warns about files that become dead code after fixing imports
Single-file analysis
- Detects unused
import Xandfrom X import Ystatements - Handles aliased imports (
import X as Y,from X import Y as Z) - Recognizes usage in:
- Function calls and attribute access
- Type annotations (including forward references / string annotations)
- Decorators and base classes
- Default argument values
__all__exports
- Skips
__future__imports (they have side effects) - Respects
# noqa: F401comments (matches flake8 behavior) - Full scope analysis with LEGB rule:
- Correctly handles function parameters that shadow imports
- Handles class scope quirks (class body doesn't enclose nested functions)
- Supports comprehension scopes and walrus operator bindings
- Respects
globalandnonlocaldeclarations
Directory exclusions
The tool automatically skips common non-source directories:
- Virtual environments:
.venv,venv,.env,env - Build artifacts:
build,dist,*.egg-info - Cache directories:
__pycache__,.mypy_cache,.pytest_cache,.ruff_cache - Version control:
.git,.hg,.svn - Other:
node_modules,.tox,.nox,.eggs
Autofix
- Safely handles empty blocks by inserting
pass - Partial removal from multi-import statements
- Handles semicolon-separated statements
- Handles backslash line continuations
Examples
Single-file example
Before:
import os
import sys # unused
from typing import List, Optional # List unused
from pathlib import Path
def get_home() -> Optional[Path]:
return Path(os.environ.get("HOME"))
After (--fix-unused-imports):
import os
from typing import Optional
from pathlib import Path
def get_home() -> Optional[Path]:
return Path(os.environ.get("HOME"))
Cross-file re-export example
# utils.py
from typing import List # NOT unused - re-exported to main.py
# main.py
from utils import List
x: List[int] = []
Running import-analyzer main.py correctly preserves the List import in utils.py because it's used by main.py.
Cascade detection example
# main.py
from helpers import List # unused - not used locally
# helpers.py
from utils import List # becomes unused when main.py's import is removed
# utils.py
from typing import List # becomes unused when helpers.py's import is removed
Running import-analyzer --fix-unused-imports main.py removes all three imports in a single pass.
Note: Imports listed in __all__ are always considered "used" (public API declaration) and won't be removed by cascade detection. This matches the behavior of flake8, ruff, and autoflake.
Indirect import fix example
The tool can fix two patterns of indirect imports:
Pattern 1: from-import style
# core.py
CONFIG = {...} # Original definition
# utils/__init__.py
from core import CONFIG # Re-exports CONFIG
# app.py (before)
from utils import CONFIG # Indirect - importing from re-exporter
Running import-analyzer --fix-indirect-imports app.py rewrites the import to use the direct source:
# app.py (after)
from core import CONFIG # Direct - importing from source
Pattern 2: import + attribute access style
# logger.py
LOGGER = Logger() # Original definition
# models/__init__.py
from logger import LOGGER # Re-exports LOGGER
# app.py (before)
import models
models.LOGGER.info("hello") # Indirect - accessing via re-exporter
Running import-analyzer --fix-indirect-imports app.py:
# app.py (after)
import models
import logger
logger.LOGGER.info("hello") # Direct - accessing via source
This also works with nested access like pkg.internal.LOGGER and handles aliases throughout the re-export chain. Function-local and class-body imports are preserved in their original scope.
noqa comments
The tool respects # noqa comments matching flake8 behavior:
import os # noqa: F401 - kept (F401 = unused import)
import sys # noqa - kept (bare noqa suppresses all)
import re # noqa: E501 - flagged (wrong code)
For multi-line imports, noqa applies per-line:
from typing import (
List, # noqa: F401 - kept
Dict, # flagged (no noqa)
)
Known Limitations
- Star imports:
from X import *cannot be analyzed statically - Dynamic imports:
importlib.import_module()calls are not tracked - Namespace packages: PEP 420 namespace packages are not supported
Development
Setup
git clone https://github.com/cmyui/import-analyzer-py
cd import-analyzer-py
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Running tests
# Run with pytest
pytest tests/ -v
# Run with tox (multiple Python versions)
tox
# Run with coverage
tox -e py
Project structure
.
├── import_analyzer/
│ ├── __init__.py # Public API exports
│ ├── __main__.py # Entry point for python -m
│ ├── _main.py # CLI and orchestration
│ ├── _data.py # Data classes (ImportInfo, ModuleInfo, etc.)
│ ├── _ast_helpers.py # AST visitors for import/usage collection
│ ├── _detection.py # Single-file detection logic
│ ├── _autofix.py # Autofix logic
│ ├── _resolution.py # Module resolution (resolves imports to files)
│ ├── _graph.py # Import graph construction
│ ├── _cross_file.py # Cross-file analysis with cascade detection
│ └── _format.py # Output formatting
├── tests/
│ ├── detection_test.py
│ ├── aliased_imports_test.py
│ ├── shadowed_imports_test.py
│ ├── scope_analysis_test.py
│ ├── special_imports_test.py
│ ├── type_annotations_test.py
│ ├── autofix_test.py
│ ├── file_operations_test.py
│ ├── cli_test.py
│ ├── resolution_test.py # Module resolution tests
│ ├── graph_test.py # Import graph tests
│ ├── cross_file_test.py # Cross-file analysis tests
│ └── format_test.py # Output formatting tests
├── pyproject.toml
├── tox.ini
└── .github/workflows/ci.yml
License
MIT
Release files for import-analyzer-py 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| import_analyzer_py-0.1.5.tar.gz | 86.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| import_analyzer_py-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.5 kB
Release files / import_analyzer_py-0.1.5.tar.gz
| Download URL | import_analyzer_py-0.1.5.tar.gz |
|---|---|
| Size | 86.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6613cc3c956fa24de61c7ad6c55c64bce1227b5a10f3738239387605cc1898f8
|
|
BLAKE2b-256 checksum How to use checksums |
1cce545fccc776db3e9acb89167665bbbdd69aa3dd6eb74165a55083e2970aa8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.
Transparency logRelease files / import_analyzer_py-0.1.5-py3-none-any.whl
| Download URL | import_analyzer_py-0.1.5-py3-none-any.whl |
|---|---|
| Size | 48.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3203c103d3cfe8c3ec64e81c8cc41172581b0046edaf06db4800559f8d1de58
|
|
BLAKE2b-256 checksum How to use checksums |
5a75f667b83d27be5216cb51d0bcdc17d1d0644414e6d0b049b923ca017df02d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.
Transparency log