import-mend 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- import_mend-0.1.0/.gitignore +11 -0
- import_mend-0.1.0/PKG-INFO +105 -0
- import_mend-0.1.0/README.md +83 -0
- import_mend-0.1.0/pyproject.toml +52 -0
- import_mend-0.1.0/src/import_mend/__init__.py +262 -0
- import_mend-0.1.0/src/import_mend/__main__.py +265 -0
- import_mend-0.1.0/src/import_mend/checker.py +754 -0
- import_mend-0.1.0/src/import_mend/differ.py +465 -0
- import_mend-0.1.0/src/import_mend/fixer.py +661 -0
- import_mend-0.1.0/src/import_mend/inventory.py +348 -0
- import_mend-0.1.0/src/import_mend/schemas.py +170 -0
- import_mend-0.1.0/tests/conftest.py +6 -0
- import_mend-0.1.0/tests/test_checker.py +509 -0
- import_mend-0.1.0/tests/test_differ.py +563 -0
- import_mend-0.1.0/tests/test_fixer.py +524 -0
- import_mend-0.1.0/tests/test_init.py +472 -0
- import_mend-0.1.0/tests/test_integration.py +302 -0
- import_mend-0.1.0/tests/test_inventory.py +495 -0
- import_mend-0.1.0/tests/test_main.py +551 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: import-mend
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Detect and repair broken Python imports after refactoring, with zero runtime cost
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: ast,developer-tools,imports,refactoring,static-analysis
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: libcst>=1.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# import-check
|
|
24
|
+
|
|
25
|
+
Detect and repair broken Python imports after refactoring — with zero runtime cost.
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
After a codebase refactoring (manual or autonomous), module paths shift. Every file that
|
|
30
|
+
imported moved symbols now references a path that no longer exists. `import-check` fixes
|
|
31
|
+
this retroactively:
|
|
32
|
+
|
|
33
|
+
1. **Inventories** symbols before and after using git history + AST (no code executed)
|
|
34
|
+
2. **Detects** moves, renames, splits, and merges in the migration map
|
|
35
|
+
3. **Rewrites** all broken imports in-place using `libcst` (format-preserving)
|
|
36
|
+
4. **Verifies** every import via filesystem + AST checks (zero runtime loading)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install import-check
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Full pipeline: fix then verify
|
|
48
|
+
python -m import_check run
|
|
49
|
+
|
|
50
|
+
# Fix broken imports only
|
|
51
|
+
python -m import_check fix
|
|
52
|
+
|
|
53
|
+
# Verify imports only (CI gate)
|
|
54
|
+
python -m import_check check
|
|
55
|
+
|
|
56
|
+
# With options
|
|
57
|
+
python -m import_check run \
|
|
58
|
+
--source-dirs src lib \
|
|
59
|
+
--git-ref HEAD~3 \
|
|
60
|
+
--format json \
|
|
61
|
+
--log-level DEBUG
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Programmatic API
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from import_check import fix, check, run
|
|
68
|
+
|
|
69
|
+
# Full pipeline
|
|
70
|
+
result = run(root="/path/to/project", source_dirs=["src"], git_ref="HEAD~1")
|
|
71
|
+
|
|
72
|
+
# Fix only
|
|
73
|
+
fix_results = fix(root=".", git_ref="abc123")
|
|
74
|
+
|
|
75
|
+
# Check only
|
|
76
|
+
errors = check(root=".", encapsulation_check=True)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Add to `pyproject.toml`:
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[tool.import_check]
|
|
85
|
+
source_dirs = ["src", "lib"]
|
|
86
|
+
exclude_patterns = [".venv", "__pycache__", "node_modules"]
|
|
87
|
+
git_ref = "HEAD"
|
|
88
|
+
encapsulation_check = true
|
|
89
|
+
output_format = "human" # or "json"
|
|
90
|
+
log_level = "INFO" # or "DEBUG", "ERROR"
|
|
91
|
+
check_stubs = true # .pyi stub fallback
|
|
92
|
+
check_getattr = true # __getattr__ suppression
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Design
|
|
96
|
+
|
|
97
|
+
- **Deterministic-first:** AST-based analysis handles the common case. LLM integration
|
|
98
|
+
for residuals is out of scope — the structured error list is the handoff contract.
|
|
99
|
+
- **Zero runtime cost:** The checker never loads a module. Safe in any environment.
|
|
100
|
+
- **Generic:** Works on any Python project with git. No project-specific config required.
|
|
101
|
+
- **Single dependency:** Only `libcst` beyond the standard library.
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# import-check
|
|
2
|
+
|
|
3
|
+
Detect and repair broken Python imports after refactoring — with zero runtime cost.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
After a codebase refactoring (manual or autonomous), module paths shift. Every file that
|
|
8
|
+
imported moved symbols now references a path that no longer exists. `import-check` fixes
|
|
9
|
+
this retroactively:
|
|
10
|
+
|
|
11
|
+
1. **Inventories** symbols before and after using git history + AST (no code executed)
|
|
12
|
+
2. **Detects** moves, renames, splits, and merges in the migration map
|
|
13
|
+
3. **Rewrites** all broken imports in-place using `libcst` (format-preserving)
|
|
14
|
+
4. **Verifies** every import via filesystem + AST checks (zero runtime loading)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install import-check
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Full pipeline: fix then verify
|
|
26
|
+
python -m import_check run
|
|
27
|
+
|
|
28
|
+
# Fix broken imports only
|
|
29
|
+
python -m import_check fix
|
|
30
|
+
|
|
31
|
+
# Verify imports only (CI gate)
|
|
32
|
+
python -m import_check check
|
|
33
|
+
|
|
34
|
+
# With options
|
|
35
|
+
python -m import_check run \
|
|
36
|
+
--source-dirs src lib \
|
|
37
|
+
--git-ref HEAD~3 \
|
|
38
|
+
--format json \
|
|
39
|
+
--log-level DEBUG
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Programmatic API
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from import_check import fix, check, run
|
|
46
|
+
|
|
47
|
+
# Full pipeline
|
|
48
|
+
result = run(root="/path/to/project", source_dirs=["src"], git_ref="HEAD~1")
|
|
49
|
+
|
|
50
|
+
# Fix only
|
|
51
|
+
fix_results = fix(root=".", git_ref="abc123")
|
|
52
|
+
|
|
53
|
+
# Check only
|
|
54
|
+
errors = check(root=".", encapsulation_check=True)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Add to `pyproject.toml`:
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
[tool.import_check]
|
|
63
|
+
source_dirs = ["src", "lib"]
|
|
64
|
+
exclude_patterns = [".venv", "__pycache__", "node_modules"]
|
|
65
|
+
git_ref = "HEAD"
|
|
66
|
+
encapsulation_check = true
|
|
67
|
+
output_format = "human" # or "json"
|
|
68
|
+
log_level = "INFO" # or "DEBUG", "ERROR"
|
|
69
|
+
check_stubs = true # .pyi stub fallback
|
|
70
|
+
check_getattr = true # __getattr__ suppression
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Design
|
|
74
|
+
|
|
75
|
+
- **Deterministic-first:** AST-based analysis handles the common case. LLM integration
|
|
76
|
+
for residuals is out of scope — the structured error list is the handoff contract.
|
|
77
|
+
- **Zero runtime cost:** The checker never loads a module. Safe in any environment.
|
|
78
|
+
- **Generic:** Works on any Python project with git. No project-specific config required.
|
|
79
|
+
- **Single dependency:** Only `libcst` beyond the standard library.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "import-mend"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Detect and repair broken Python imports after refactoring, with zero runtime cost"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
keywords = ["imports", "refactoring", "ast", "static-analysis", "developer-tools"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"libcst>=1.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=7.0",
|
|
31
|
+
"pytest-cov",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
import-mend = "import_mend.__main__:main"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/import_mend"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
addopts = "-v"
|
|
43
|
+
|
|
44
|
+
[tool.import_mend]
|
|
45
|
+
source_dirs = ["src"]
|
|
46
|
+
exclude_patterns = [".venv", "__pycache__", "node_modules", ".git"]
|
|
47
|
+
git_ref = "HEAD"
|
|
48
|
+
encapsulation_check = true
|
|
49
|
+
output_format = "human"
|
|
50
|
+
log_level = "INFO"
|
|
51
|
+
check_stubs = true
|
|
52
|
+
check_getattr = true
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @summary
|
|
2
|
+
# Public API facade for the import_mend tool.
|
|
3
|
+
# Provides three entry points: fix() for deterministic import rewriting,
|
|
4
|
+
# check() for smoke-testing all imports, and run() for fix-then-check.
|
|
5
|
+
# Loads configuration from pyproject.toml [tool.import_mend] with overrides.
|
|
6
|
+
# Exports: fix, check, run, ImportCheckConfig, RunResult, FixResult, ImportError
|
|
7
|
+
# Deps: tomllib/tomli, logging, pathlib, import_mend.schemas,
|
|
8
|
+
# import_mend.inventory, import_mend.differ, import_mend.fixer,
|
|
9
|
+
# import_mend.checker
|
|
10
|
+
# @end-summary
|
|
11
|
+
|
|
12
|
+
"""Public API facade for the import_mend tool.
|
|
13
|
+
|
|
14
|
+
Provides three entry points:
|
|
15
|
+
|
|
16
|
+
- :func:`fix` — deterministic import rewriting based on git-diff inventory.
|
|
17
|
+
- :func:`check` — smoke-test all imports for resolution and encapsulation.
|
|
18
|
+
- :func:`run` — fix then check (the full pipeline).
|
|
19
|
+
|
|
20
|
+
Configuration is loaded from ``pyproject.toml`` ``[tool.import_mend]`` section
|
|
21
|
+
with programmatic overrides applied on top.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from dataclasses import fields
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from .schemas import (
|
|
31
|
+
FixResult,
|
|
32
|
+
ImportCheckConfig,
|
|
33
|
+
ImportError,
|
|
34
|
+
RunResult,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"fix",
|
|
39
|
+
"check",
|
|
40
|
+
"run",
|
|
41
|
+
"ImportCheckConfig",
|
|
42
|
+
"RunResult",
|
|
43
|
+
"FixResult",
|
|
44
|
+
"ImportError",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Internal helpers
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _load_config(root: Path, **overrides: object) -> ImportCheckConfig:
|
|
54
|
+
"""Load configuration with resolution order: defaults < pyproject.toml < overrides.
|
|
55
|
+
|
|
56
|
+
Reads ``[tool.import_mend]`` from ``pyproject.toml`` at *root* (if it exists),
|
|
57
|
+
merges with dataclass defaults, then applies any keyword overrides.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
root: Project root directory containing ``pyproject.toml``.
|
|
61
|
+
**overrides: Keyword arguments that override both defaults and file values.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Fully resolved :class:`ImportCheckConfig`.
|
|
65
|
+
"""
|
|
66
|
+
# --- 1. Start with dataclass defaults (implicit via constructor) ---
|
|
67
|
+
defaults: dict[str, object] = {}
|
|
68
|
+
|
|
69
|
+
# --- 2. Read pyproject.toml if present ---
|
|
70
|
+
pyproject_path = root / "pyproject.toml"
|
|
71
|
+
file_values: dict[str, object] = {}
|
|
72
|
+
|
|
73
|
+
if pyproject_path.is_file():
|
|
74
|
+
try:
|
|
75
|
+
import tomllib # Python 3.11+
|
|
76
|
+
except ModuleNotFoundError:
|
|
77
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
with open(pyproject_path, "rb") as f:
|
|
81
|
+
data = tomllib.load(f)
|
|
82
|
+
file_values = data.get("tool", {}).get("import_mend", {})
|
|
83
|
+
except Exception: # noqa: BLE001
|
|
84
|
+
logging.getLogger("import_mend").warning(
|
|
85
|
+
"Failed to parse pyproject.toml at %s, using defaults", pyproject_path,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# --- 3. Merge: defaults < file < overrides ---
|
|
89
|
+
# Collect valid field names from the dataclass.
|
|
90
|
+
valid_fields = {f.name for f in fields(ImportCheckConfig)}
|
|
91
|
+
|
|
92
|
+
merged: dict[str, object] = {}
|
|
93
|
+
|
|
94
|
+
# Apply file values (only recognised keys).
|
|
95
|
+
for key, value in file_values.items():
|
|
96
|
+
if key in valid_fields:
|
|
97
|
+
merged[key] = value
|
|
98
|
+
|
|
99
|
+
# Apply programmatic overrides (highest priority).
|
|
100
|
+
for key, value in overrides.items():
|
|
101
|
+
if key in valid_fields:
|
|
102
|
+
merged[key] = value
|
|
103
|
+
|
|
104
|
+
# Always set root.
|
|
105
|
+
merged["root"] = root
|
|
106
|
+
|
|
107
|
+
return ImportCheckConfig(**merged) # type: ignore[arg-type]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _setup_logging(config: ImportCheckConfig) -> logging.Logger:
|
|
111
|
+
"""Configure the ``import_mend`` logger with the level from *config*.
|
|
112
|
+
|
|
113
|
+
Format: ``%(levelname)s: %(message)s``.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
config: Configuration with ``log_level`` field.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The configured :class:`logging.Logger`.
|
|
120
|
+
"""
|
|
121
|
+
logger = logging.getLogger("import_mend")
|
|
122
|
+
logger.setLevel(config.log_level.upper())
|
|
123
|
+
|
|
124
|
+
# Avoid duplicate handlers on repeated calls.
|
|
125
|
+
if not logger.handlers:
|
|
126
|
+
handler = logging.StreamHandler()
|
|
127
|
+
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
|
|
128
|
+
logger.addHandler(handler)
|
|
129
|
+
|
|
130
|
+
return logger
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# Public entry points
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def fix(root: str | Path | None = None, **config_overrides: object) -> FixResult:
|
|
139
|
+
"""Deterministic fix pipeline: diff git inventories, then rewrite imports.
|
|
140
|
+
|
|
141
|
+
Steps:
|
|
142
|
+
1. Resolve *root* (defaults to cwd).
|
|
143
|
+
2. Load configuration.
|
|
144
|
+
3. Setup logging.
|
|
145
|
+
4. Get changed files since ``git_ref``.
|
|
146
|
+
5. Build old (git) and new (filesystem) symbol inventories.
|
|
147
|
+
6. Diff inventories to produce a migration map.
|
|
148
|
+
7. If no migrations, return an empty :class:`FixResult`.
|
|
149
|
+
8. Collect all Python files across configured source directories.
|
|
150
|
+
9. Apply import fixes using the migration map.
|
|
151
|
+
10. Return the :class:`FixResult`.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
root: Project root directory. Defaults to the current working directory.
|
|
155
|
+
**config_overrides: Overrides forwarded to :func:`_load_config`.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
:class:`FixResult` summarising files modified and fixes applied.
|
|
159
|
+
"""
|
|
160
|
+
from . import differ, fixer, inventory
|
|
161
|
+
|
|
162
|
+
# 1. Resolve root.
|
|
163
|
+
resolved_root = Path(root).resolve() if root is not None else Path.cwd().resolve()
|
|
164
|
+
|
|
165
|
+
# 2. Load config.
|
|
166
|
+
config = _load_config(resolved_root, **config_overrides)
|
|
167
|
+
|
|
168
|
+
# 3. Setup logging.
|
|
169
|
+
logger = _setup_logging(config)
|
|
170
|
+
|
|
171
|
+
# 4. Get changed files.
|
|
172
|
+
changed_files = inventory.get_changed_files(config.git_ref, resolved_root)
|
|
173
|
+
logger.info("Found %d changed files", len(changed_files))
|
|
174
|
+
|
|
175
|
+
if not changed_files:
|
|
176
|
+
return FixResult()
|
|
177
|
+
|
|
178
|
+
# 5. Build old and new inventories.
|
|
179
|
+
old_inv = inventory.build_old_inventory(changed_files, config.git_ref, resolved_root)
|
|
180
|
+
logger.info("Built old inventory: %d symbols", sum(len(v) for v in old_inv.values()))
|
|
181
|
+
|
|
182
|
+
new_inv = inventory.build_inventory(changed_files, resolved_root)
|
|
183
|
+
logger.info("Built new inventory: %d symbols", sum(len(v) for v in new_inv.values()))
|
|
184
|
+
|
|
185
|
+
# 6. Diff inventories.
|
|
186
|
+
migration_map = differ.diff_inventories(old_inv, new_inv)
|
|
187
|
+
logger.info("Migration map: %d entries", len(migration_map))
|
|
188
|
+
|
|
189
|
+
# 7. If no migrations, return empty result.
|
|
190
|
+
if not migration_map:
|
|
191
|
+
return FixResult()
|
|
192
|
+
|
|
193
|
+
# 8. Collect all Python files.
|
|
194
|
+
all_files = inventory.collect_python_files(
|
|
195
|
+
config.source_dirs, resolved_root, config.exclude_patterns,
|
|
196
|
+
)
|
|
197
|
+
logger.info("Applying fixes to %d files", len(all_files))
|
|
198
|
+
|
|
199
|
+
# 9. Apply fixes.
|
|
200
|
+
result = fixer.apply_fixes(migration_map, all_files, resolved_root)
|
|
201
|
+
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def check(
|
|
206
|
+
root: str | Path | None = None, **config_overrides: object
|
|
207
|
+
) -> list[ImportError]:
|
|
208
|
+
"""Smoke-test all imports for resolution and encapsulation errors.
|
|
209
|
+
|
|
210
|
+
Steps:
|
|
211
|
+
1. Resolve *root* (defaults to cwd).
|
|
212
|
+
2. Load configuration.
|
|
213
|
+
3. Setup logging.
|
|
214
|
+
4. Collect all Python files across configured source directories.
|
|
215
|
+
5. Run import checks.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
root: Project root directory. Defaults to the current working directory.
|
|
219
|
+
**config_overrides: Overrides forwarded to :func:`_load_config`.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
List of :class:`ImportError` records. Empty if all imports are clean.
|
|
223
|
+
"""
|
|
224
|
+
from . import checker, inventory
|
|
225
|
+
|
|
226
|
+
# 1. Resolve root.
|
|
227
|
+
resolved_root = Path(root).resolve() if root is not None else Path.cwd().resolve()
|
|
228
|
+
|
|
229
|
+
# 2. Load config.
|
|
230
|
+
config = _load_config(resolved_root, **config_overrides)
|
|
231
|
+
|
|
232
|
+
# 3. Setup logging.
|
|
233
|
+
logger = _setup_logging(config)
|
|
234
|
+
|
|
235
|
+
# 4. Collect all Python files.
|
|
236
|
+
all_files = inventory.collect_python_files(
|
|
237
|
+
config.source_dirs, resolved_root, config.exclude_patterns,
|
|
238
|
+
)
|
|
239
|
+
logger.info("Checking imports in %d files", len(all_files))
|
|
240
|
+
|
|
241
|
+
# 5. Run checker.
|
|
242
|
+
errors = checker.check_imports(all_files, resolved_root, config)
|
|
243
|
+
|
|
244
|
+
logger.info("Found %d import errors", len(errors))
|
|
245
|
+
|
|
246
|
+
return errors
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def run(root: str | Path | None = None, **config_overrides: object) -> RunResult:
|
|
250
|
+
"""Run the full pipeline: fix imports, then check for remaining errors.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
root: Project root directory. Defaults to the current working directory.
|
|
254
|
+
**config_overrides: Overrides forwarded to :func:`_load_config`.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
:class:`RunResult` containing the fix result and any remaining errors.
|
|
258
|
+
"""
|
|
259
|
+
fix_result = fix(root=root, **config_overrides)
|
|
260
|
+
remaining_errors = check(root=root, **config_overrides)
|
|
261
|
+
|
|
262
|
+
return RunResult(fix_result=fix_result, remaining_errors=remaining_errors)
|