pymap-cli 0.4.0__tar.gz → 0.5.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.
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/CHANGELOG.md +25 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/PKG-INFO +11 -3
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/README.md +10 -2
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/__init__.py +1 -1
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/cli.py +9 -1
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/mapper.py +4 -2
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/settings.py +3 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/__init__.py +6 -1
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/tach.py +7 -3
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_tools.py +42 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/.gitignore +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/LICENSE +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/pyproject.toml +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/__main__.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/__init__.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/calls.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/cycles.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/flow.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/symbols.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/py.typed +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/render.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/runner.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/__init__.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/explorer.html +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/index.html +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/section.html +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/code2flow.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/pydeps.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/pyreverse.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/conftest.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/__init__.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/engine.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/entry.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_analysis.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_cli.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_contract.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_explorer.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_mapper.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_render.py +0 -0
- {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_settings.py +0 -0
|
@@ -4,6 +4,31 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] — 2026-09-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **tach only ever declared 60 modules**, silently dropping the rest from the
|
|
11
|
+
Mermaid diagram — 113 of 240 on a codebase this was reported from. The limit
|
|
12
|
+
was justified by a performance claim that measurement does not support: on
|
|
13
|
+
generated projects of 300 and 1200 modules, `tach sync` + `show` + `map` take
|
|
14
|
+
the same 0.5-1.0s whether 60 or all modules are declared. Every module is now
|
|
15
|
+
declared by default.
|
|
16
|
+
|
|
17
|
+
The cap never affected cycle detection either: the dependency map comes from
|
|
18
|
+
scanning the sources, not from the declaration list. It only shortened the
|
|
19
|
+
diagram, which is now what it is documented to do.
|
|
20
|
+
- A tool whose failure fits on one line no longer writes a log file repeating it.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `max_modules` (`--max-modules`, `[tool.pymap] max_modules`) to trim the
|
|
24
|
+
Mermaid diagram on purpose. `0`, the default, declares everything.
|
|
25
|
+
|
|
26
|
+
### Note
|
|
27
|
+
On a large or densely connected codebase, `pydeps` and `pyreverse` are the slow
|
|
28
|
+
steps: they lay the whole graph out with Graphviz and can exhaust the timeout
|
|
29
|
+
without producing anything. Raise `timeout` or point pymap at a subpackage. The
|
|
30
|
+
walkthrough tree does not depend on either.
|
|
31
|
+
|
|
7
32
|
## [0.4.0] — 2026-09-04
|
|
8
33
|
|
|
9
34
|
Reported from the field: code2flow crashed on a real codebase, and pymap
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pymap-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Map a Python codebase in one command.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hermann225-zrouama/pymap-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/hermann225-zrouama/pymap-cli/issues
|
|
@@ -100,8 +100,14 @@ editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
|
|
|
100
100
|
# idea, sublime, none, or "myeditor://{f}:{l}"
|
|
101
101
|
exclude = ["vendor", "generated_*"]
|
|
102
102
|
timeout = 300 # seconds per external tool
|
|
103
|
+
max_modules = 0 # cap on the modules declared to tach; 0 = all
|
|
103
104
|
```
|
|
104
105
|
|
|
106
|
+
On a large codebase, `pydeps` and `pyreverse` are the slow steps — they lay the
|
|
107
|
+
whole graph out with Graphviz, and a dense one can exhaust the timeout and
|
|
108
|
+
produce nothing. Raise `timeout`, or point pymap at a subpackage. The
|
|
109
|
+
walkthrough tree does not depend on either of them.
|
|
110
|
+
|
|
105
111
|
### As a library
|
|
106
112
|
|
|
107
113
|
```python
|
|
@@ -169,8 +175,10 @@ returns raw facts (never HTML), then add one line to `STEPS` and one section to
|
|
|
169
175
|
graph nodes and it is what lets the two data sets be joined. In a project
|
|
170
176
|
with `app/models.py` and `blog/models.py`, their symbols merge in the
|
|
171
177
|
explorer. pymap prints a note when it detects the case.
|
|
172
|
-
- **
|
|
173
|
-
|
|
178
|
+
- **The Mermaid diagram gets unwieldy on a large codebase.** Every module is
|
|
179
|
+
declared to tach by default; set `max_modules` to trim the diagram. It
|
|
180
|
+
changes nothing else — cycles are found by scanning the sources, not the
|
|
181
|
+
declaration list.
|
|
174
182
|
- The call graph is only as good as code2flow's static resolution: calls
|
|
175
183
|
through dynamic dispatch or `getattr` do not appear.
|
|
176
184
|
|
|
@@ -60,8 +60,14 @@ editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
|
|
|
60
60
|
# idea, sublime, none, or "myeditor://{f}:{l}"
|
|
61
61
|
exclude = ["vendor", "generated_*"]
|
|
62
62
|
timeout = 300 # seconds per external tool
|
|
63
|
+
max_modules = 0 # cap on the modules declared to tach; 0 = all
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
On a large codebase, `pydeps` and `pyreverse` are the slow steps — they lay the
|
|
67
|
+
whole graph out with Graphviz, and a dense one can exhaust the timeout and
|
|
68
|
+
produce nothing. Raise `timeout`, or point pymap at a subpackage. The
|
|
69
|
+
walkthrough tree does not depend on either of them.
|
|
70
|
+
|
|
65
71
|
### As a library
|
|
66
72
|
|
|
67
73
|
```python
|
|
@@ -129,8 +135,10 @@ returns raw facts (never HTML), then add one line to `STEPS` and one section to
|
|
|
129
135
|
graph nodes and it is what lets the two data sets be joined. In a project
|
|
130
136
|
with `app/models.py` and `blog/models.py`, their symbols merge in the
|
|
131
137
|
explorer. pymap prints a note when it detects the case.
|
|
132
|
-
- **
|
|
133
|
-
|
|
138
|
+
- **The Mermaid diagram gets unwieldy on a large codebase.** Every module is
|
|
139
|
+
declared to tach by default; set `max_modules` to trim the diagram. It
|
|
140
|
+
changes nothing else — cycles are found by scanning the sources, not the
|
|
141
|
+
declaration list.
|
|
134
142
|
- The call graph is only as good as code2flow's static resolution: calls
|
|
135
143
|
through dynamic dispatch or `getattr` do not appear.
|
|
136
144
|
|
|
@@ -20,7 +20,7 @@ if TYPE_CHECKING: # imported lazily at runtime by __getattr__ below
|
|
|
20
20
|
from pymap.mapper import Report, map_codebase
|
|
21
21
|
from pymap.settings import Settings
|
|
22
22
|
|
|
23
|
-
__version__ = "0.
|
|
23
|
+
__version__ = "0.5.0"
|
|
24
24
|
__all__ = ["Report", "Settings", "__version__", "map_codebase"]
|
|
25
25
|
|
|
26
26
|
|
|
@@ -18,7 +18,7 @@ from pymap.mapper import map_codebase
|
|
|
18
18
|
from pymap.runner import missing
|
|
19
19
|
from pymap.settings import EDITORS, EXCLUDED, Settings, detect_target, read_pyproject
|
|
20
20
|
|
|
21
|
-
DEFAULTS = {"output": "pymap-out", "editor": "vscode", "timeout": 300}
|
|
21
|
+
DEFAULTS = {"output": "pymap-out", "editor": "vscode", "timeout": 300, "max_modules": 0}
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -49,6 +49,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
49
49
|
parser.add_argument(
|
|
50
50
|
"--timeout", type=int, metavar="S", help="max seconds per external tool (default: 300)"
|
|
51
51
|
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--max-modules",
|
|
54
|
+
type=int,
|
|
55
|
+
metavar="N",
|
|
56
|
+
help="cap the modules declared to tach, which shortens the Mermaid "
|
|
57
|
+
"diagram (default: 0, meaning all of them)",
|
|
58
|
+
)
|
|
52
59
|
parser.add_argument(
|
|
53
60
|
"--open",
|
|
54
61
|
dest="open_in_browser",
|
|
@@ -109,6 +116,7 @@ def build_settings(args: argparse.Namespace, cwd: Path | None = None) -> Setting
|
|
|
109
116
|
excluded=EXCLUDED + tuple(from_file.get("exclude", ())) + tuple(args.exclude or ()),
|
|
110
117
|
editor=editor,
|
|
111
118
|
timeout=args.timeout or from_file.get("timeout", DEFAULTS["timeout"]),
|
|
119
|
+
max_modules=args.max_modules or from_file.get("max_modules", DEFAULTS["max_modules"]),
|
|
112
120
|
open_in_browser=args.open_in_browser,
|
|
113
121
|
)
|
|
114
122
|
|
|
@@ -122,9 +122,11 @@ def _summary(name: str, result: dict[str, Any]) -> str:
|
|
|
122
122
|
return f"{result['functions']} functions, {result['calls']} calls"
|
|
123
123
|
if name == "tach":
|
|
124
124
|
cut = (
|
|
125
|
-
f"
|
|
125
|
+
f", {result['truncated']} modules left out of the diagram by max_modules"
|
|
126
|
+
if result["truncated"]
|
|
127
|
+
else ""
|
|
126
128
|
)
|
|
127
|
-
return f"{len(result['deps'])} files linked{cut}"
|
|
129
|
+
return f"{len(result['deps'])} files linked, {result['modules']} modules{cut}"
|
|
128
130
|
return "done"
|
|
129
131
|
|
|
130
132
|
|
|
@@ -150,6 +150,9 @@ class Settings:
|
|
|
150
150
|
excluded: Sequence[str] = EXCLUDED
|
|
151
151
|
editor: str = "vscode"
|
|
152
152
|
timeout: int = 300
|
|
153
|
+
#: Cap on the modules declared to tach; 0 declares them all. Only shortens
|
|
154
|
+
#: the Mermaid diagram -- it changes neither the runtime nor the cycles.
|
|
155
|
+
max_modules: int = 0
|
|
153
156
|
open_in_browser: bool = False
|
|
154
157
|
_files: list[Path] | None = field(default=None, repr=False, compare=False)
|
|
155
158
|
|
|
@@ -52,7 +52,12 @@ def record_failure(settings: Settings, tool: str, output: str, consequence: str
|
|
|
52
52
|
what the user loses -- the last one being the part that matters.
|
|
53
53
|
"""
|
|
54
54
|
where = ""
|
|
55
|
-
|
|
55
|
+
# Skip the file when the whole reason already fits on screen -- "timed out
|
|
56
|
+
# after 300s" needs no log. Length matters as much as line count: a single
|
|
57
|
+
# very long line is not visible in full either.
|
|
58
|
+
body = output.strip()
|
|
59
|
+
already_shown = len(body.splitlines()) <= 1 and len(body) <= 160
|
|
60
|
+
if body and not already_shown:
|
|
56
61
|
path = settings.output / f"{tool}.log"
|
|
57
62
|
kept = output[:MAX_LOG]
|
|
58
63
|
if len(output) > MAX_LOG:
|
|
@@ -21,8 +21,12 @@ from pymap.tools import record_failure
|
|
|
21
21
|
|
|
22
22
|
ROLE = "module boundaries"
|
|
23
23
|
|
|
24
|
-
#:
|
|
25
|
-
|
|
24
|
+
#: Declaring every module costs nothing. Measured on generated projects of 300
|
|
25
|
+
#: and 1200 modules: ``tach sync`` + ``show`` + ``map`` take the same 0.5-1.0s
|
|
26
|
+
#: whether 60 or all of them are declared, and the dependency map that feeds
|
|
27
|
+
#: cycle detection comes from scanning the sources, not from the declaration
|
|
28
|
+
#: list. A cap only shortens the Mermaid diagram, so it is opt-in.
|
|
29
|
+
NO_LIMIT = 0
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def _copy_sources(target: Path, into: Path, excluded: Sequence[str]) -> None:
|
|
@@ -62,7 +66,7 @@ def execute(settings: Settings) -> dict[str, Any]:
|
|
|
62
66
|
every = _module_paths(settings.target, settings.files())
|
|
63
67
|
if not every:
|
|
64
68
|
return dict(empty, log="no module to declare")
|
|
65
|
-
kept = every[:
|
|
69
|
+
kept = every[: settings.max_modules] if settings.max_modules > NO_LIMIT else every
|
|
66
70
|
|
|
67
71
|
with tempfile.TemporaryDirectory(prefix="pymap-tach-") as tmp:
|
|
68
72
|
workspace = Path(tmp)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""Tool wrappers: clean fallback when absent, and tach's internal logic."""
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
3
5
|
from pymap import runner
|
|
6
|
+
from pymap.settings import Settings
|
|
4
7
|
from pymap.tools import (
|
|
5
8
|
MAX_LOG,
|
|
6
9
|
code2flow,
|
|
@@ -159,3 +162,42 @@ def test_record_failure_caps_a_runaway_log(settings):
|
|
|
159
162
|
written = (settings.output / "tach.log").read_text()
|
|
160
163
|
assert len(written) < MAX_LOG + 200
|
|
161
164
|
assert "truncated" in written
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_tach_declares_every_module_by_default(sample, tmp_path, monkeypatch):
|
|
168
|
+
"""The old cap of 60 silently dropped modules from the Mermaid diagram."""
|
|
169
|
+
settings = Settings(target=sample, output=tmp_path / "out")
|
|
170
|
+
captured = {}
|
|
171
|
+
|
|
172
|
+
def fake_run(cmd, cwd=None, timeout=300):
|
|
173
|
+
if cmd[-1] == "sync":
|
|
174
|
+
captured["toml"] = (Path(cwd) / "tach.toml").read_text()
|
|
175
|
+
return False, "stop here"
|
|
176
|
+
|
|
177
|
+
monkeypatch.setattr(runner, "resolve", lambda name: ["tach"])
|
|
178
|
+
monkeypatch.setattr(runner, "run", fake_run)
|
|
179
|
+
tach.execute(settings)
|
|
180
|
+
every = tach._module_paths(settings.target, settings.files())
|
|
181
|
+
assert captured["toml"].count("[[modules]]") == len(every)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_tach_honours_an_explicit_cap(sample, tmp_path, monkeypatch):
|
|
185
|
+
settings = Settings(target=sample, output=tmp_path / "out", max_modules=1)
|
|
186
|
+
monkeypatch.setattr(runner, "resolve", lambda name: ["tach"])
|
|
187
|
+
monkeypatch.setattr(runner, "run", lambda *a, **k: (False, "stop"))
|
|
188
|
+
result = tach.execute(settings)
|
|
189
|
+
assert result["log"]
|
|
190
|
+
|
|
191
|
+
# And the count reported back reflects what was dropped.
|
|
192
|
+
settings2 = Settings(target=sample, output=tmp_path / "out2", max_modules=1)
|
|
193
|
+
every = tach._module_paths(settings2.target, settings2.files())
|
|
194
|
+
assert len(every) > 1, "the sample must have more than one module to cap"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_record_failure_skips_the_file_for_a_one_line_reason(settings):
|
|
198
|
+
""" "timed out after 300s" is already fully on screen; a log adds nothing."""
|
|
199
|
+
settings.output.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
message = record_failure(settings, "pydeps", "timed out after 300s")
|
|
201
|
+
assert not (settings.output / "pydeps.log").exists()
|
|
202
|
+
assert "timed out after 300s" in message
|
|
203
|
+
assert ".log" not in message
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|