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.
Files changed (40) hide show
  1. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/CHANGELOG.md +25 -0
  2. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/PKG-INFO +11 -3
  3. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/README.md +10 -2
  4. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/__init__.py +1 -1
  5. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/cli.py +9 -1
  6. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/mapper.py +4 -2
  7. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/settings.py +3 -0
  8. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/__init__.py +6 -1
  9. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/tach.py +7 -3
  10. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_tools.py +42 -0
  11. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/.gitignore +0 -0
  12. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/LICENSE +0 -0
  13. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/pyproject.toml +0 -0
  14. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/__main__.py +0 -0
  15. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/__init__.py +0 -0
  16. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/calls.py +0 -0
  17. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/cycles.py +0 -0
  18. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/flow.py +0 -0
  19. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/analysis/symbols.py +0 -0
  20. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/py.typed +0 -0
  21. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/render.py +0 -0
  22. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/runner.py +0 -0
  23. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/__init__.py +0 -0
  24. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/explorer.html +0 -0
  25. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/index.html +0 -0
  26. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/templates/section.html +0 -0
  27. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/code2flow.py +0 -0
  28. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/pydeps.py +0 -0
  29. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/src/pymap/tools/pyreverse.py +0 -0
  30. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/conftest.py +0 -0
  31. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/__init__.py +0 -0
  32. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/engine.py +0 -0
  33. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/sample/entry.py +0 -0
  34. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_analysis.py +0 -0
  35. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_cli.py +0 -0
  36. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_contract.py +0 -0
  37. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_explorer.py +0 -0
  38. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_mapper.py +0 -0
  39. {pymap_cli-0.4.0 → pymap_cli-0.5.0}/tests/test_render.py +0 -0
  40. {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.4.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
- - **tach declares at most 60 modules.** Beyond that `tach sync` gets very slow
173
- for a graph that is already unreadable. The count left out is reported.
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
- - **tach declares at most 60 modules.** Beyond that `tach sync` gets very slow
133
- for a graph that is already unreadable. The count left out is reported.
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.4.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" ({result['truncated']} modules beyond the limit)" if result["truncated"] else ""
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
- if output.strip():
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
- #: Past this point ``tach sync`` gets very slow, for a graph already unreadable.
25
- MAX_MODULES = 60
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[:MAX_MODULES]
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