pymap-cli 0.3.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.3.0/.gitignore +26 -0
- pymap_cli-0.3.0/CHANGELOG.md +72 -0
- pymap_cli-0.3.0/LICENSE +21 -0
- pymap_cli-0.3.0/PKG-INFO +197 -0
- pymap_cli-0.3.0/README.md +157 -0
- pymap_cli-0.3.0/pyproject.toml +97 -0
- pymap_cli-0.3.0/src/pymap/__init__.py +37 -0
- pymap_cli-0.3.0/src/pymap/__main__.py +8 -0
- pymap_cli-0.3.0/src/pymap/analysis/__init__.py +1 -0
- pymap_cli-0.3.0/src/pymap/analysis/calls.py +36 -0
- pymap_cli-0.3.0/src/pymap/analysis/cycles.py +43 -0
- pymap_cli-0.3.0/src/pymap/analysis/flow.py +175 -0
- pymap_cli-0.3.0/src/pymap/analysis/symbols.py +175 -0
- pymap_cli-0.3.0/src/pymap/cli.py +143 -0
- pymap_cli-0.3.0/src/pymap/mapper.py +198 -0
- pymap_cli-0.3.0/src/pymap/py.typed +0 -0
- pymap_cli-0.3.0/src/pymap/render.py +170 -0
- pymap_cli-0.3.0/src/pymap/runner.py +93 -0
- pymap_cli-0.3.0/src/pymap/settings.py +176 -0
- pymap_cli-0.3.0/src/pymap/templates/__init__.py +8 -0
- pymap_cli-0.3.0/src/pymap/templates/explorer.html +822 -0
- pymap_cli-0.3.0/src/pymap/templates/index.html +42 -0
- pymap_cli-0.3.0/src/pymap/templates/section.html +7 -0
- pymap_cli-0.3.0/src/pymap/tools/__init__.py +22 -0
- pymap_cli-0.3.0/src/pymap/tools/code2flow.py +56 -0
- pymap_cli-0.3.0/src/pymap/tools/pydeps.py +42 -0
- pymap_cli-0.3.0/src/pymap/tools/pyreverse.py +41 -0
- pymap_cli-0.3.0/src/pymap/tools/tach.py +104 -0
- pymap_cli-0.3.0/tests/conftest.py +23 -0
- pymap_cli-0.3.0/tests/sample/__init__.py +1 -0
- pymap_cli-0.3.0/tests/sample/engine.py +34 -0
- pymap_cli-0.3.0/tests/sample/entry.py +9 -0
- pymap_cli-0.3.0/tests/test_analysis.py +194 -0
- pymap_cli-0.3.0/tests/test_cli.py +82 -0
- pymap_cli-0.3.0/tests/test_contract.py +109 -0
- pymap_cli-0.3.0/tests/test_mapper.py +99 -0
- pymap_cli-0.3.0/tests/test_render.py +142 -0
- pymap_cli-0.3.0/tests/test_settings.py +88 -0
- pymap_cli-0.3.0/tests/test_tools.py +91 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pymap output
|
|
2
|
+
pymap-out/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# environments and caches
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
.tox/
|
|
16
|
+
.nox/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
|
|
21
|
+
# editor local state
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
|
|
25
|
+
# OS
|
|
26
|
+
.DS_Store
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.3.0] — 2026-09-04
|
|
8
|
+
|
|
9
|
+
First release prepared for publication. The codebase, its comments and the
|
|
10
|
+
generated interface are now entirely in English.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `tests/test_contract.py`, pinning the key names shared between the Python
|
|
14
|
+
payload and `templates/explorer.html`, and running `node --check` on the
|
|
15
|
+
generated script when Node is available.
|
|
16
|
+
- A notice when several files share a basename, since their symbols merge into
|
|
17
|
+
one namespace in the explorer.
|
|
18
|
+
- `ruff` configuration (lint + format), type annotations across the public API,
|
|
19
|
+
and a `py.typed` marker.
|
|
20
|
+
- `LICENSE` (MIT), `CONTRIBUTING.md`, this changelog.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- A docstring containing `</script>` closed the `<script>` block early and
|
|
24
|
+
blanked the whole explorer page. Payloads now go through `script_json()`,
|
|
25
|
+
which escapes `<`, `>`, `&` and the U+2028/U+2029 line separators.
|
|
26
|
+
- Keyboard navigation of the flow (<kbd>↓</kbd> <kbd>↑</kbd> <kbd>↵</kbd>) was
|
|
27
|
+
advertised in the help panel but did nothing: the step list was never filled.
|
|
28
|
+
- Marking a symbol as seen (<kbd>m</kbd>) updated the progress bar but left the
|
|
29
|
+
tree unchanged; seen symbols are now dimmed and ticked.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Renamed for English: `Reglages` → `Settings`, `cartographier` →
|
|
33
|
+
`map_codebase`, `Rapport` → `Report`, and the modules `reglages`/`processus`/
|
|
34
|
+
`carte`/`rendu`/`analyse`/`outils`/`gabarits` → `settings`/`runner`/`mapper`/
|
|
35
|
+
`render`/`analysis`/`tools`/`templates`.
|
|
36
|
+
- The call-graph JSON is now written as `calls.json` (was `appels.json`), and
|
|
37
|
+
the explorer page as `explorer.html` (was `explorateur.html`).
|
|
38
|
+
|
|
39
|
+
## [0.2.0] — 2026-09-04
|
|
40
|
+
|
|
41
|
+
Restructured from a single 1,339-line script into an installable package.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- `pyproject.toml`, a `src/` layout and a `pymap` console script, so pymap can
|
|
45
|
+
be installed into any Python project.
|
|
46
|
+
- Zero-argument invocation: the target package is detected from
|
|
47
|
+
`[tool.pymap] target`, a `src/` layout, or the project name.
|
|
48
|
+
- Configuration through `[tool.pymap]` in the target's `pyproject.toml`.
|
|
49
|
+
- `--exclude`, `--editor` and `--timeout` options; code links support eight
|
|
50
|
+
editors or a custom URI template.
|
|
51
|
+
- Exclusion of environments and caches (`.venv/`, `node_modules/`, `build/`, …),
|
|
52
|
+
pruned during the walk rather than after it.
|
|
53
|
+
- A test suite covering the degraded path with no external tool installed.
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
- `pyreverse` was declared among the required tools and advertised in the
|
|
57
|
+
documentation, but no step ever ran it. It now produces the class and package
|
|
58
|
+
diagrams.
|
|
59
|
+
- `tach` wrote a `tach.toml` two directories above the target, saving and
|
|
60
|
+
restoring any existing one — a crash in between destroyed the user's file.
|
|
61
|
+
The sources are now copied into a temporary directory.
|
|
62
|
+
- External tools were not found when pymap ran from a virtualenv whose `bin/`
|
|
63
|
+
was not on `PATH`, and the `python -m` fallback failed on packages without a
|
|
64
|
+
`__main__` module.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
- The HTML templates moved out of the Python source into real `.html` files,
|
|
68
|
+
substituted through `__PYMAP_X__` tokens instead of `str.format`.
|
|
69
|
+
|
|
70
|
+
## [0.1.0]
|
|
71
|
+
|
|
72
|
+
Initial single-file version.
|
pymap_cli-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Franck Zrouama
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pymap_cli-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pymap-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Map a Python codebase in one command.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hermann225-zrouama/pymap-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/hermann225-zrouama/pymap-cli/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/hermann225-zrouama/pymap-cli/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Franck Zrouama
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: architecture,ast,call graph,codebase map,documentation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: code2flow>=2.5; extra == 'dev'
|
|
26
|
+
Requires-Dist: pydeps>=1.11; extra == 'dev'
|
|
27
|
+
Requires-Dist: pylint>=2.15; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Requires-Dist: tach>=0.9; extra == 'dev'
|
|
31
|
+
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
|
|
32
|
+
Provides-Extra: graphs
|
|
33
|
+
Requires-Dist: code2flow>=2.5; extra == 'graphs'
|
|
34
|
+
Requires-Dist: pydeps>=1.11; extra == 'graphs'
|
|
35
|
+
Requires-Dist: pylint>=2.15; extra == 'graphs'
|
|
36
|
+
Requires-Dist: tach>=0.9; extra == 'graphs'
|
|
37
|
+
Provides-Extra: toml
|
|
38
|
+
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'toml'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# pymap
|
|
42
|
+
|
|
43
|
+
Map a Python codebase in one command. No service, no upload: everything is
|
|
44
|
+
analysed and rendered locally.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd my-project
|
|
48
|
+
pymap --open
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
pymap guesses which package to analyse, runs whichever graph tools you have
|
|
52
|
+
installed, and assembles the result into a single page — plus a **flow
|
|
53
|
+
explorer** that pymap builds itself: for every function, the order of the calls,
|
|
54
|
+
the conditions, the loops, the error paths, and the data flowing through.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pipx install pymap-cli # isolated, available everywhere
|
|
60
|
+
# or, inside the project you want to map:
|
|
61
|
+
pip install pymap-cli
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The core depends only on the standard library, so installing pymap into a
|
|
65
|
+
project adds **no version constraint** to it. The graph tools are optional and
|
|
66
|
+
live behind an extra:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install "pymap-cli[graphs]" # pydeps, code2flow, pylint, tach
|
|
70
|
+
brew install graphviz # or: apt install graphviz
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Any missing tool is reported at start-up and then skipped; the explorer always
|
|
74
|
+
works.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pymap # guess the current project's package
|
|
80
|
+
pymap src/mypkg # explicit target
|
|
81
|
+
pymap src/mypkg -o map/ --open # output directory, open when done
|
|
82
|
+
pymap --exclude "generated_*" # skip more paths
|
|
83
|
+
pymap --editor pycharm # code links for another editor
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
How the target is guessed, in order: `[tool.pymap] target` in `pyproject.toml`,
|
|
87
|
+
a `src/<package>/` layout, the package named after the project, then the single
|
|
88
|
+
top-level package. If nothing stands out, pymap says so and waits for a path.
|
|
89
|
+
|
|
90
|
+
### Configuration
|
|
91
|
+
|
|
92
|
+
Optional, in the project's `pyproject.toml` (read on Python ≥ 3.11, or with
|
|
93
|
+
`tomli` installed):
|
|
94
|
+
|
|
95
|
+
```toml
|
|
96
|
+
[tool.pymap]
|
|
97
|
+
target = "src/mypkg"
|
|
98
|
+
output = "pymap-out"
|
|
99
|
+
editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
|
|
100
|
+
# idea, sublime, none, or "myeditor://{f}:{l}"
|
|
101
|
+
exclude = ["vendor", "generated_*"]
|
|
102
|
+
timeout = 300 # seconds per external tool
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### As a library
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from pymap import Settings, map_codebase
|
|
109
|
+
|
|
110
|
+
report = map_codebase(Settings(target="src/mypkg", output="map/"))
|
|
111
|
+
print(report.coverage, "% documented,", len(report.cycles), "cycles")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## What the map contains
|
|
115
|
+
|
|
116
|
+
| View | Source | What you read there |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| **Walkthrough tree** | pymap (AST) | The execution flow function by function, keyboard-navigable |
|
|
119
|
+
| Imports between modules | pydeps | The layers, and the modules everyone pulls in |
|
|
120
|
+
| Classes and inheritance | pyreverse | Attributes and hierarchies, when the code is object-oriented |
|
|
121
|
+
| Module boundaries | tach | File-by-file dependencies, cycles, Mermaid source |
|
|
122
|
+
|
|
123
|
+
The output directory (`pymap-out/` by default) holds `index.html` — the page to
|
|
124
|
+
open — and the views it links to.
|
|
125
|
+
|
|
126
|
+
### In the explorer
|
|
127
|
+
|
|
128
|
+
<kbd>↓</kbd> <kbd>↑</kbd> walk the steps · <kbd>↵</kbd> step into the
|
|
129
|
+
highlighted call · <kbd>←</kbd> go back up · <kbd>m</kbd> mark seen ·
|
|
130
|
+
<kbd>o</kbd> open in the editor · <kbd>/</kbd> search
|
|
131
|
+
|
|
132
|
+
## How the code is organised
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
src/pymap/
|
|
136
|
+
├── cli.py command line: arguments, pyproject, messages
|
|
137
|
+
├── settings.py what to analyse, where to write, what to skip
|
|
138
|
+
├── mapper.py orchestration + library API
|
|
139
|
+
├── runner.py launching external tools, detecting missing ones
|
|
140
|
+
├── render.py templates → HTML pages
|
|
141
|
+
├── analysis/ what pymap works out on its own, from the AST
|
|
142
|
+
│ ├── symbols.py modules, classes, functions, signatures
|
|
143
|
+
│ ├── flow.py execution tree of a function
|
|
144
|
+
│ ├── calls.py code2flow's call graph
|
|
145
|
+
│ └── cycles.py circular dependencies
|
|
146
|
+
├── tools/ one module per external tool
|
|
147
|
+
│ ├── pydeps.py pyreverse.py code2flow.py tach.py
|
|
148
|
+
└── templates/ index.html, section.html, explorer.html
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Adding a tool: drop a module in `tools/` exposing `execute(settings)` that
|
|
152
|
+
returns raw facts (never HTML), then add one line to `STEPS` and one section to
|
|
153
|
+
`_sections()`, both in `mapper.py`. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
154
|
+
|
|
155
|
+
## Guarantees
|
|
156
|
+
|
|
157
|
+
- **Nothing is executed** from the analysed code: everything goes through the
|
|
158
|
+
standard library's AST.
|
|
159
|
+
- **Nothing is written** into the target project. `tach` requires a `tach.toml`
|
|
160
|
+
at the root of wherever it runs, so pymap copies the sources into a temporary
|
|
161
|
+
directory instead of dropping that file in your tree.
|
|
162
|
+
- Environments and caches (`.venv/`, `node_modules/`, `build/`,
|
|
163
|
+
`__pycache__/`, …) are pruned during the walk, never traversed.
|
|
164
|
+
|
|
165
|
+
## Known limitations
|
|
166
|
+
|
|
167
|
+
- **Files sharing a basename share a namespace.** Symbol keys are
|
|
168
|
+
`<file stem>::<qualified name>`, because that is how code2flow names its
|
|
169
|
+
graph nodes and it is what lets the two data sets be joined. In a project
|
|
170
|
+
with `app/models.py` and `blog/models.py`, their symbols merge in the
|
|
171
|
+
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.
|
|
174
|
+
- The call graph is only as good as code2flow's static resolution: calls
|
|
175
|
+
through dynamic dispatch or `getattr` do not appear.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git clone https://github.com/hermann225-zrouama/pymap-cli
|
|
181
|
+
cd pymap-cli
|
|
182
|
+
pip install -e ".[dev]"
|
|
183
|
+
pytest
|
|
184
|
+
ruff check src tests && ruff format --check src tests
|
|
185
|
+
pymap # pymap maps itself
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`tests/test_contract.py` pins the key names shared between the Python payload
|
|
189
|
+
and `templates/explorer.html`. A rename on one side without the other produces
|
|
190
|
+
a blank page rather than an error, so that test is what keeps them honest.
|
|
191
|
+
|
|
192
|
+
## Licence
|
|
193
|
+
|
|
194
|
+
MIT — see [LICENSE](LICENSE).
|
|
195
|
+
|
|
196
|
+
The distribution is named `pymap-cli` because `pymap` is already taken on PyPI
|
|
197
|
+
by an IMAP library. The command and the import name are both `pymap`.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# pymap
|
|
2
|
+
|
|
3
|
+
Map a Python codebase in one command. No service, no upload: everything is
|
|
4
|
+
analysed and rendered locally.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
cd my-project
|
|
8
|
+
pymap --open
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
pymap guesses which package to analyse, runs whichever graph tools you have
|
|
12
|
+
installed, and assembles the result into a single page — plus a **flow
|
|
13
|
+
explorer** that pymap builds itself: for every function, the order of the calls,
|
|
14
|
+
the conditions, the loops, the error paths, and the data flowing through.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pipx install pymap-cli # isolated, available everywhere
|
|
20
|
+
# or, inside the project you want to map:
|
|
21
|
+
pip install pymap-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The core depends only on the standard library, so installing pymap into a
|
|
25
|
+
project adds **no version constraint** to it. The graph tools are optional and
|
|
26
|
+
live behind an extra:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install "pymap-cli[graphs]" # pydeps, code2flow, pylint, tach
|
|
30
|
+
brew install graphviz # or: apt install graphviz
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Any missing tool is reported at start-up and then skipped; the explorer always
|
|
34
|
+
works.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pymap # guess the current project's package
|
|
40
|
+
pymap src/mypkg # explicit target
|
|
41
|
+
pymap src/mypkg -o map/ --open # output directory, open when done
|
|
42
|
+
pymap --exclude "generated_*" # skip more paths
|
|
43
|
+
pymap --editor pycharm # code links for another editor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
How the target is guessed, in order: `[tool.pymap] target` in `pyproject.toml`,
|
|
47
|
+
a `src/<package>/` layout, the package named after the project, then the single
|
|
48
|
+
top-level package. If nothing stands out, pymap says so and waits for a path.
|
|
49
|
+
|
|
50
|
+
### Configuration
|
|
51
|
+
|
|
52
|
+
Optional, in the project's `pyproject.toml` (read on Python ≥ 3.11, or with
|
|
53
|
+
`tomli` installed):
|
|
54
|
+
|
|
55
|
+
```toml
|
|
56
|
+
[tool.pymap]
|
|
57
|
+
target = "src/mypkg"
|
|
58
|
+
output = "pymap-out"
|
|
59
|
+
editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
|
|
60
|
+
# idea, sublime, none, or "myeditor://{f}:{l}"
|
|
61
|
+
exclude = ["vendor", "generated_*"]
|
|
62
|
+
timeout = 300 # seconds per external tool
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### As a library
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from pymap import Settings, map_codebase
|
|
69
|
+
|
|
70
|
+
report = map_codebase(Settings(target="src/mypkg", output="map/"))
|
|
71
|
+
print(report.coverage, "% documented,", len(report.cycles), "cycles")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## What the map contains
|
|
75
|
+
|
|
76
|
+
| View | Source | What you read there |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| **Walkthrough tree** | pymap (AST) | The execution flow function by function, keyboard-navigable |
|
|
79
|
+
| Imports between modules | pydeps | The layers, and the modules everyone pulls in |
|
|
80
|
+
| Classes and inheritance | pyreverse | Attributes and hierarchies, when the code is object-oriented |
|
|
81
|
+
| Module boundaries | tach | File-by-file dependencies, cycles, Mermaid source |
|
|
82
|
+
|
|
83
|
+
The output directory (`pymap-out/` by default) holds `index.html` — the page to
|
|
84
|
+
open — and the views it links to.
|
|
85
|
+
|
|
86
|
+
### In the explorer
|
|
87
|
+
|
|
88
|
+
<kbd>↓</kbd> <kbd>↑</kbd> walk the steps · <kbd>↵</kbd> step into the
|
|
89
|
+
highlighted call · <kbd>←</kbd> go back up · <kbd>m</kbd> mark seen ·
|
|
90
|
+
<kbd>o</kbd> open in the editor · <kbd>/</kbd> search
|
|
91
|
+
|
|
92
|
+
## How the code is organised
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
src/pymap/
|
|
96
|
+
├── cli.py command line: arguments, pyproject, messages
|
|
97
|
+
├── settings.py what to analyse, where to write, what to skip
|
|
98
|
+
├── mapper.py orchestration + library API
|
|
99
|
+
├── runner.py launching external tools, detecting missing ones
|
|
100
|
+
├── render.py templates → HTML pages
|
|
101
|
+
├── analysis/ what pymap works out on its own, from the AST
|
|
102
|
+
│ ├── symbols.py modules, classes, functions, signatures
|
|
103
|
+
│ ├── flow.py execution tree of a function
|
|
104
|
+
│ ├── calls.py code2flow's call graph
|
|
105
|
+
│ └── cycles.py circular dependencies
|
|
106
|
+
├── tools/ one module per external tool
|
|
107
|
+
│ ├── pydeps.py pyreverse.py code2flow.py tach.py
|
|
108
|
+
└── templates/ index.html, section.html, explorer.html
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Adding a tool: drop a module in `tools/` exposing `execute(settings)` that
|
|
112
|
+
returns raw facts (never HTML), then add one line to `STEPS` and one section to
|
|
113
|
+
`_sections()`, both in `mapper.py`. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
114
|
+
|
|
115
|
+
## Guarantees
|
|
116
|
+
|
|
117
|
+
- **Nothing is executed** from the analysed code: everything goes through the
|
|
118
|
+
standard library's AST.
|
|
119
|
+
- **Nothing is written** into the target project. `tach` requires a `tach.toml`
|
|
120
|
+
at the root of wherever it runs, so pymap copies the sources into a temporary
|
|
121
|
+
directory instead of dropping that file in your tree.
|
|
122
|
+
- Environments and caches (`.venv/`, `node_modules/`, `build/`,
|
|
123
|
+
`__pycache__/`, …) are pruned during the walk, never traversed.
|
|
124
|
+
|
|
125
|
+
## Known limitations
|
|
126
|
+
|
|
127
|
+
- **Files sharing a basename share a namespace.** Symbol keys are
|
|
128
|
+
`<file stem>::<qualified name>`, because that is how code2flow names its
|
|
129
|
+
graph nodes and it is what lets the two data sets be joined. In a project
|
|
130
|
+
with `app/models.py` and `blog/models.py`, their symbols merge in the
|
|
131
|
+
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.
|
|
134
|
+
- The call graph is only as good as code2flow's static resolution: calls
|
|
135
|
+
through dynamic dispatch or `getattr` do not appear.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/hermann225-zrouama/pymap-cli
|
|
141
|
+
cd pymap-cli
|
|
142
|
+
pip install -e ".[dev]"
|
|
143
|
+
pytest
|
|
144
|
+
ruff check src tests && ruff format --check src tests
|
|
145
|
+
pymap # pymap maps itself
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`tests/test_contract.py` pins the key names shared between the Python payload
|
|
149
|
+
and `templates/explorer.html`. A rename on one side without the other produces
|
|
150
|
+
a blank page rather than an error, so that test is what keeps them honest.
|
|
151
|
+
|
|
152
|
+
## Licence
|
|
153
|
+
|
|
154
|
+
MIT — see [LICENSE](LICENSE).
|
|
155
|
+
|
|
156
|
+
The distribution is named `pymap-cli` because `pymap` is already taken on PyPI
|
|
157
|
+
by an IMAP library. The command and the import name are both `pymap`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# The import name and the command stay "pymap"; only the distribution name is
|
|
7
|
+
# suffixed, because "pymap" is already taken on PyPI by an IMAP library.
|
|
8
|
+
name = "pymap-cli"
|
|
9
|
+
# Single source of truth: src/pymap/__init__.py. Bumping two files by hand is
|
|
10
|
+
# how a release ends up reporting a version it was not built from.
|
|
11
|
+
dynamic = ["version"]
|
|
12
|
+
description = "Map a Python codebase in one command."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
17
|
+
authors = [{ name = "Franck Zrouama" }]
|
|
18
|
+
keywords = ["codebase map", "documentation", "call graph", "architecture", "ast"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Software Development :: Documentation",
|
|
30
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
31
|
+
]
|
|
32
|
+
# The core runs on the standard library: installing pymap into a project adds
|
|
33
|
+
# no version constraint to it.
|
|
34
|
+
dependencies = []
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
# The four external tools pymap knows how to drive. Without them it still
|
|
38
|
+
# produces the flow explorer.
|
|
39
|
+
graphs = ["pydeps>=1.11", "code2flow>=2.5", "pylint>=2.15", "tach>=0.9"]
|
|
40
|
+
toml = ["tomli>=2.0; python_version < '3.11'"]
|
|
41
|
+
dev = ["pytest>=7", "ruff>=0.6", "pymap-cli[graphs,toml]"]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
pymap = "pymap.cli:main"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/hermann225-zrouama/pymap-cli"
|
|
48
|
+
Issues = "https://github.com/hermann225-zrouama/pymap-cli/issues"
|
|
49
|
+
Changelog = "https://github.com/hermann225-zrouama/pymap-cli/blob/main/CHANGELOG.md"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.version]
|
|
52
|
+
path = "src/pymap/__init__.py"
|
|
53
|
+
|
|
54
|
+
[tool.hatch.build.targets.wheel]
|
|
55
|
+
packages = ["src/pymap"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.sdist]
|
|
58
|
+
include = ["src/pymap", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
59
|
+
|
|
60
|
+
# pymap maps itself: run `pymap` with no argument at the repository root.
|
|
61
|
+
[tool.pymap]
|
|
62
|
+
target = "src/pymap"
|
|
63
|
+
output = "pymap-out"
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
addopts = "-q"
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 96
|
|
71
|
+
target-version = "py39"
|
|
72
|
+
src = ["src", "tests"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = [
|
|
76
|
+
"E", "W", # pycodestyle
|
|
77
|
+
"F", # pyflakes
|
|
78
|
+
"I", # import sorting
|
|
79
|
+
"UP", # pyupgrade
|
|
80
|
+
"B", # bugbear
|
|
81
|
+
"C4", # comprehensions
|
|
82
|
+
"SIM", # simplify
|
|
83
|
+
"RUF", # ruff-specific
|
|
84
|
+
]
|
|
85
|
+
ignore = [
|
|
86
|
+
"E501", # line length is handled by the formatter
|
|
87
|
+
# Long lists of short words (builtin names, ignored directories) are kept as
|
|
88
|
+
# a single split() string on purpose: the literal form expands to one item
|
|
89
|
+
# per line under the formatter, and the suggested fix is a 400-column line
|
|
90
|
+
# that the formatter immediately explodes again.
|
|
91
|
+
"SIM905",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.per-file-ignores]
|
|
95
|
+
# Tests deliberately reach into private helpers and build odd inputs.
|
|
96
|
+
"tests/*" = ["B011", "SIM117"]
|
|
97
|
+
"tests/sample/*" = ["D", "B", "SIM"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""pymap -- map a Python codebase in one command.
|
|
2
|
+
|
|
3
|
+
It drives the tools that actually work on Python and gathers their output into
|
|
4
|
+
a single page, plus a flow explorer that pymap builds itself:
|
|
5
|
+
|
|
6
|
+
pydeps import graph between modules
|
|
7
|
+
pyreverse class and package diagrams (ships with pylint)
|
|
8
|
+
code2flow call graph at function level
|
|
9
|
+
tach module graph + Mermaid, cycle detection
|
|
10
|
+
|
|
11
|
+
The core depends only on the standard library: missing tools are reported and
|
|
12
|
+
then skipped.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # imported lazily at runtime by __getattr__ below
|
|
20
|
+
from pymap.mapper import Report, map_codebase
|
|
21
|
+
from pymap.settings import Settings
|
|
22
|
+
|
|
23
|
+
__version__ = "0.3.0"
|
|
24
|
+
__all__ = ["Report", "Settings", "__version__", "map_codebase"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def __getattr__(name: str) -> Any:
|
|
28
|
+
# Lazy import, so `import pymap` stays instant when all you want is __version__.
|
|
29
|
+
if name in ("Report", "map_codebase"):
|
|
30
|
+
from pymap import mapper
|
|
31
|
+
|
|
32
|
+
return getattr(mapper, name)
|
|
33
|
+
if name == "Settings":
|
|
34
|
+
from pymap.settings import Settings
|
|
35
|
+
|
|
36
|
+
return Settings
|
|
37
|
+
raise AttributeError(f"module 'pymap' has no attribute {name!r}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Static analysis: everything pymap works out on its own, without any tool."""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Reading the call graph produced by code2flow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
Relations = dict[str, dict[str, list[str]]]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_call_graph(path: str | Path) -> Relations:
|
|
12
|
+
"""Read code2flow's JSON and return outgoing and incoming calls per symbol.
|
|
13
|
+
|
|
14
|
+
Result shape: ``{symbol: {"calls": [...], "called_by": [...]}}``. A missing
|
|
15
|
+
or unreadable file yields an empty graph: the explorer works without it, it
|
|
16
|
+
just loses navigation between functions.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
graph = json.loads(Path(path).read_text(encoding="utf-8"))["graph"]
|
|
20
|
+
except (OSError, ValueError, KeyError):
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
names = {node_id: node["name"] for node_id, node in graph["nodes"].items()}
|
|
24
|
+
relations: dict[str, dict[str, set[str]]] = {}
|
|
25
|
+
for edge in graph["edges"]:
|
|
26
|
+
source, target = names.get(edge["source"]), names.get(edge["target"])
|
|
27
|
+
if not source or not target:
|
|
28
|
+
continue
|
|
29
|
+
relations.setdefault(source, {"calls": set(), "called_by": set()})["calls"].add(target)
|
|
30
|
+
relations.setdefault(target, {"calls": set(), "called_by": set()})["called_by"].add(
|
|
31
|
+
source
|
|
32
|
+
)
|
|
33
|
+
return {
|
|
34
|
+
name: {side: sorted(values) for side, values in sides.items()}
|
|
35
|
+
for name, sides in relations.items()
|
|
36
|
+
}
|