codeanalyzer-python 1.1.1__tar.gz → 1.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.
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/.gitignore +4 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/PKG-INFO +143 -164
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/README.md +139 -154
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/__main__.py +119 -118
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/__init__.py +20 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/config_keys.py +588 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/config_use.py +597 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/config_use_rules.yml +58 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/dependencies.py +237 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/discovery.py +167 -0
- codeanalyzer_python-1.3.0/codeanalyzer/artifacts/parsers.py +248 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/core.py +112 -45
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/access_paths.py +26 -4
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/builder.py +22 -1
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/identity.py +1 -1
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/pdg.py +7 -2
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scc.py +1 -1
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/__init__.py +3 -0
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/detect.py +124 -0
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/matching.py +182 -0
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/pipeline.py +131 -0
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/rules.py +159 -0
- codeanalyzer_python-1.3.0/codeanalyzer/entrypoints/rules.yml +88 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/bolt.py +1 -1
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/project.py +277 -60
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/schema.py +92 -34
- codeanalyzer_python-1.3.0/codeanalyzer/options/__init__.py +3 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/options/options.py +7 -26
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/schema/__init__.py +48 -0
- codeanalyzer_python-1.3.0/codeanalyzer/schema/ids.py +44 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/schema/l1_body.py +11 -1
- codeanalyzer_python-1.3.0/codeanalyzer/schema/l2_callees.py +52 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/schema/py_schema.py +213 -103
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/semantic_analysis/call_graph.py +20 -4
- codeanalyzer_python-1.3.0/codeanalyzer/semantic_analysis/defuse_linker.py +1499 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/syntactic_analysis/symbol_table_builder.py +99 -3
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/pyproject.toml +18 -14
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/schema.neo4j.json +182 -55
- codeanalyzer_python-1.1.1/codeanalyzer/config/__init__.py +0 -3
- codeanalyzer_python-1.1.1/codeanalyzer/config/config.py +0 -8
- codeanalyzer_python-1.1.1/codeanalyzer/options/__init__.py +0 -3
- codeanalyzer_python-1.1.1/codeanalyzer/schema/ids.py +0 -23
- codeanalyzer_python-1.1.1/codeanalyzer/schema/l2_callees.py +0 -36
- codeanalyzer_python-1.1.1/codeanalyzer/semantic_analysis/pycg/__init__.py +0 -20
- codeanalyzer_python-1.1.1/codeanalyzer/semantic_analysis/pycg/pycg_analysis.py +0 -1115
- codeanalyzer_python-1.1.1/codeanalyzer/semantic_analysis/pycg/pycg_exceptions.py +0 -23
- codeanalyzer_python-1.1.1/codeanalyzer/semantic_analysis/pycg/shard_planner.py +0 -401
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/NOTICE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/alias.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/cfg.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/defuse.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/dominance.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/SSA/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/SSA/const.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/cfg/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/cfg/builder.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/cfg/model.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/core/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/core/func_call_visitor.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel/core/vars_visitor.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/scalpel_oracle.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/sdg.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/slicing.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/summaries.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/dataflow/syntactic.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/jedi/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/jedi/jedi.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/cypher.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/emit.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/neo4j/rows.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/provenance.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/py.typed +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/schema/assign_ids.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/schema/call_graph_ids.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/semantic_analysis/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/syntactic_analysis/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/syntactic_analysis/exceptions.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/syntactic_analysis/import_resolver.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/utils/__init__.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/utils/logging.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/codeanalyzer/utils/progress_bar.py +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/docs/handoff/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/docs/handoff/schema.neo4j.json +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/schema-uml.drawio +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/flask/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/flask/examples/celery/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/flask/requirements-skip/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/flask/src/flask/sansio/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/requests/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/requests/NOTICE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/requests/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/requests/docs/_themes/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/requests/ext/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/properties/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/xarray/datatree_/LICENSE +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/xarray/datatree_/README.md +0 -0
- {codeanalyzer_python-1.1.1 → codeanalyzer_python-1.3.0}/test/fixtures/whole_applications/xarray/xarray/datatree_/docs/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: codeanalyzer-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Static analysis for Python — canonical schema v2 (symbol table, call graph, and native CFG/PDG/SDG dataflow) as analysis.json or a Neo4j property graph.
|
|
5
5
|
Author-email: Rahul Krishna <i.m.ralk@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -9,25 +9,19 @@ Requires-Python: >=3.9
|
|
|
9
9
|
Requires-Dist: astor<0.9.0,>=0.8.1
|
|
10
10
|
Requires-Dist: jedi<0.20.0,>=0.18.0; python_version < '3.11'
|
|
11
11
|
Requires-Dist: jedi<=0.19.2; python_version >= '3.11'
|
|
12
|
-
Requires-Dist: msgpack<1.0.7,>=1.0.0; python_version < '3.11'
|
|
13
|
-
Requires-Dist: msgpack<2.0.0,>=1.0.7; python_version >= '3.11'
|
|
14
12
|
Requires-Dist: networkx<3.2.0,>=2.6.0; python_version < '3.11'
|
|
15
13
|
Requires-Dist: networkx<4.0.0,>=3.0.0; python_version >= '3.11'
|
|
16
|
-
Requires-Dist: numpy<1.24.0,>=1.21.0; python_version < '3.11'
|
|
17
|
-
Requires-Dist: numpy<2.0.0,>=1.24.0; python_version >= '3.11' and python_version < '3.12'
|
|
18
|
-
Requires-Dist: numpy<2.0.0,>=1.26.0; python_version >= '3.12'
|
|
19
14
|
Requires-Dist: packaging>=25.0
|
|
20
|
-
Requires-Dist: pandas<2.0.0,>=1.3.0; python_version < '3.11'
|
|
21
|
-
Requires-Dist: pandas<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
22
15
|
Requires-Dist: parso>=0.8.5
|
|
23
|
-
Requires-Dist: pycg>=0.0.6
|
|
24
16
|
Requires-Dist: pydantic<2.0.0,>=1.8.0; python_version < '3.11'
|
|
25
17
|
Requires-Dist: pydantic<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
18
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
26
19
|
Requires-Dist: ray<3.0.0,>=2.10.0; python_version >= '3.11'
|
|
27
20
|
Requires-Dist: ray==2.0.0; python_version < '3.11'
|
|
28
21
|
Requires-Dist: requests<3.0.0,>=2.20.0; python_version >= '3.11'
|
|
29
22
|
Requires-Dist: rich<14.0.0,>=12.6.0; python_version < '3.11'
|
|
30
23
|
Requires-Dist: rich<15.0.0,>=14.0.0; python_version >= '3.11'
|
|
24
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
25
|
Requires-Dist: typer<1.0.0,>=0.9.0; python_version < '3.11'
|
|
32
26
|
Requires-Dist: typer<2.0.0,>=0.9.0; python_version >= '3.11'
|
|
33
27
|
Requires-Dist: typing-extensions<5.0.0,>=4.0.0; python_version < '3.11'
|
|
@@ -55,7 +49,7 @@ Description-Content-Type: text/markdown
|
|
|
55
49
|
---
|
|
56
50
|
|
|
57
51
|
`canpy` is a static analyzer for Python built on [Jedi](https://jedi.readthedocs.io/),
|
|
58
|
-
|
|
52
|
+
and [Tree-sitter](https://tree-sitter.github.io/). It
|
|
59
53
|
emits the **canonical CodeLLM-DevKit (CLDK) schema v2** — a single, additive Code Property Graph
|
|
60
54
|
tree — either as `analysis.json` or projected into a **Neo4j property graph**. It is the Python
|
|
61
55
|
backend behind [CLDK](https://github.com/codellm-devkit/python-sdk), mirroring its
|
|
@@ -97,8 +91,9 @@ needs.
|
|
|
97
91
|
at a single `application` node with durable `can://` ids on every callable and above.
|
|
98
92
|
- **Symbol table** — modules, classes, functions, methods, variables, decorators, imports, and
|
|
99
93
|
docstrings, with precise byte-offset source spans; each module carries its `source` once.
|
|
100
|
-
- **Call graph** — Jedi's lexical resolver at level 1, enriched
|
|
101
|
-
|
|
94
|
+
- **Call graph** — Jedi's lexical resolver at level 1, enriched at level 2 by a per-callable
|
|
95
|
+
**defuse linker** that resolves call sites through local def-use chains and module-scope
|
|
96
|
+
bindings (provenance-tagged, deterministic, no global fixpoint).
|
|
102
97
|
- **Dataflow graphs** — native, per-callable exceptional **CFG** plus **control-** and
|
|
103
98
|
**data-dependence** edges (`cfg`/`cdg`/`ddg`) at level 3, stitched into a whole-program
|
|
104
99
|
**interprocedural SDG** (synthetic parameter vertices, `param_in`/`param_out`/`summary`,
|
|
@@ -109,7 +104,7 @@ needs.
|
|
|
109
104
|
checked in as `schema.neo4j.json` (`2.0.0`) and shipped with every release.
|
|
110
105
|
- **Incremental cache** — per-file results are cached under `.codeanalyzer`; `--lazy` (default)
|
|
111
106
|
reuses them, `--eager` forces a clean rebuild. `--ray` distributes the work across cores.
|
|
112
|
-
- **Compact output** — canonical `analysis.json
|
|
107
|
+
- **Compact output** — one canonical `analysis.json` per run.
|
|
113
108
|
|
|
114
109
|
## Installation
|
|
115
110
|
|
|
@@ -182,8 +177,7 @@ canpy --input /path/to/python/project
|
|
|
182
177
|
```
|
|
183
178
|
|
|
184
179
|
With no `--output`, the analysis is printed to stdout as compact JSON; with `--output <dir>` it is
|
|
185
|
-
written to `analysis.json` (or `graph.cypher` for `--emit neo4j
|
|
186
|
-
`--format msgpack`) in that directory.
|
|
180
|
+
written to `analysis.json` (or `graph.cypher` for `--emit neo4j`) in that directory.
|
|
187
181
|
|
|
188
182
|
### Options
|
|
189
183
|
|
|
@@ -194,7 +188,7 @@ $ canpy --help
|
|
|
194
188
|
|
|
195
189
|
Usage: canpy [OPTIONS] COMMAND [ARGS]...
|
|
196
190
|
|
|
197
|
-
Static Analysis on Python source code using Jedi
|
|
191
|
+
Static Analysis on Python source code using Jedi and Tree sitter.
|
|
198
192
|
|
|
199
193
|
╭─ Options ────────────────────────────────────────────────────────────────────╮
|
|
200
194
|
│ --version Show the canpy │
|
|
@@ -207,10 +201,6 @@ $ canpy --help
|
|
|
207
201
|
│ --emit schema). │
|
|
208
202
|
│ --output -o <path> Output directory │
|
|
209
203
|
│ for artifacts. │
|
|
210
|
-
│ --format -f <json|msgpack> Output format │
|
|
211
|
-
│ for --emit json: │
|
|
212
|
-
│ json or msgpack. │
|
|
213
|
-
│ [default: json] │
|
|
214
204
|
│ --emit <json|neo4j|sche Output target: │
|
|
215
205
|
│ ma> json │
|
|
216
206
|
│ (analysis.json, │
|
|
@@ -258,7 +248,8 @@ $ canpy --help
|
|
|
258
248
|
│ --analysis-level -a <int range> Analysis depth: │
|
|
259
249
|
│ [1<=x<=4] 1=symbol │
|
|
260
250
|
│ table+Jedi call │
|
|
261
|
-
│ graph,
|
|
251
|
+
│ graph, │
|
|
252
|
+
│ 2=+defuse-linker │
|
|
262
253
|
│ call graph, │
|
|
263
254
|
│ 3=+native │
|
|
264
255
|
│ intraprocedural │
|
|
@@ -270,7 +261,7 @@ $ canpy --help
|
|
|
270
261
|
│ edges, │
|
|
271
262
|
│ alias-aware │
|
|
272
263
|
│ DDG). │
|
|
273
|
-
│ [default: 1]
|
|
264
|
+
│ [default: (1)] │
|
|
274
265
|
│ --graphs <str> Level 3+ only: │
|
|
275
266
|
│ comma-separated │
|
|
276
267
|
│ program-graph │
|
|
@@ -282,8 +273,12 @@ $ canpy --help
|
|
|
282
273
|
│ PDG's data edges │
|
|
283
274
|
│ only; `sdg` │
|
|
284
275
|
│ requires -a 4. │
|
|
276
|
+
│ Incompatible │
|
|
277
|
+
│ with --emit │
|
|
278
|
+
│ neo4j (always │
|
|
279
|
+
│ full-depth). │
|
|
285
280
|
│ [default: │
|
|
286
|
-
│ cfg,dfg,pdg]
|
|
281
|
+
│ (cfg,dfg,pdg)] │
|
|
287
282
|
│ --graph-field-de… <int range> Level 3 only: │
|
|
288
283
|
│ [x>=1] k-limit on │
|
|
289
284
|
│ access-path │
|
|
@@ -321,6 +316,16 @@ $ canpy --help
|
|
|
321
316
|
│ environment │
|
|
322
317
|
│ instead. │
|
|
323
318
|
│ [default: venv] │
|
|
319
|
+
│ --resolve-instal… Additionally │
|
|
320
|
+
│ bind imports via │
|
|
321
|
+
│ the project │
|
|
322
|
+
│ venv's installed │
|
|
323
|
+
│ metadata │
|
|
324
|
+
│ (*.dist-info); │
|
|
325
|
+
│ output becomes │
|
|
326
|
+
│ machine-depende… │
|
|
327
|
+
│ (prov: │
|
|
328
|
+
│ installed-metad… │
|
|
324
329
|
│ --file-name <path> Analyze only the │
|
|
325
330
|
│ specified file │
|
|
326
331
|
│ (relative to │
|
|
@@ -344,134 +349,38 @@ $ canpy --help
|
|
|
344
349
|
│ verbosity: -v, │
|
|
345
350
|
│ -vv, -vvv │
|
|
346
351
|
│ [default: 0] │
|
|
347
|
-
│ --
|
|
348
|
-
│
|
|
349
|
-
│
|
|
350
|
-
│
|
|
351
|
-
│
|
|
352
|
-
│
|
|
353
|
-
│
|
|
354
|
-
│
|
|
355
|
-
│
|
|
356
|
-
│
|
|
357
|
-
│
|
|
358
|
-
│
|
|
359
|
-
│
|
|
360
|
-
│
|
|
361
|
-
│
|
|
362
|
-
│
|
|
363
|
-
│
|
|
364
|
-
│ flag, projects │
|
|
365
|
-
│ over the ceiling │
|
|
366
|
-
│ fall back to │
|
|
367
|
-
│ Jedi-only edges. │
|
|
352
|
+
│ --entrypoint-rul… <path> Extra entrypoint │
|
|
353
|
+
│ rules file │
|
|
354
|
+
│ (YAML). │
|
|
355
|
+
│ Repeatable; │
|
|
356
|
+
│ merges with the │
|
|
357
|
+
│ shipped rules. A │
|
|
358
|
+
│ malformed file │
|
|
359
|
+
│ is an error. │
|
|
360
|
+
│ --artifact-text --no-artifact-… Capture verbatim │
|
|
361
|
+
│ `source` text on │
|
|
362
|
+
│ discovered │
|
|
363
|
+
│ artifacts. │
|
|
364
|
+
│ --no-artifact-t… │
|
|
365
|
+
│ empties `source` │
|
|
366
|
+
│ everywhere │
|
|
367
|
+
│ (inventory │
|
|
368
|
+
│ unchanged). │
|
|
368
369
|
│ [default: │
|
|
369
|
-
│
|
|
370
|
-
│ --
|
|
371
|
-
│ [x>=1]
|
|
372
|
-
│
|
|
373
|
-
│
|
|
374
|
-
│
|
|
375
|
-
│
|
|
376
|
-
│
|
|
377
|
-
│
|
|
378
|
-
│
|
|
379
|
-
│
|
|
380
|
-
│
|
|
381
|
-
│
|
|
382
|
-
│
|
|
383
|
-
│ still included). │
|
|
384
|
-
│ Lower values are │
|
|
385
|
-
│ safer for │
|
|
386
|
-
│ packages with │
|
|
387
|
-
│ deep class │
|
|
388
|
-
│ hierarchies or │
|
|
389
|
-
│ heavy import │
|
|
390
|
-
│ graphs. │
|
|
391
|
-
│ [default: 100] │
|
|
392
|
-
│ --pycg-shard-tim… <int range> Per-shard │
|
|
393
|
-
│ [x>=0] wall-clock │
|
|
394
|
-
│ timeout in │
|
|
395
|
-
│ seconds when │
|
|
396
|
-
│ --pycg-shard is │
|
|
397
|
-
│ active (default │
|
|
398
|
-
│ 120). A shard │
|
|
399
|
-
│ that exceeds │
|
|
400
|
-
│ this limit is │
|
|
401
|
-
│ skipped │
|
|
402
|
-
│ gracefully. │
|
|
403
|
-
│ PyCG's fixpoint │
|
|
404
|
-
│ is bimodal: it │
|
|
405
|
-
│ either converges │
|
|
406
|
-
│ quickly or │
|
|
407
|
-
│ diverges │
|
|
408
|
-
│ indefinitely, so │
|
|
409
|
-
│ the timeout acts │
|
|
410
|
-
│ as a final │
|
|
411
|
-
│ safety net after │
|
|
412
|
-
│ the file-count │
|
|
413
|
-
│ ceiling. Set to │
|
|
414
|
-
│ 0 to disable. │
|
|
415
|
-
│ POSIX only │
|
|
416
|
-
│ (macOS / Linux); │
|
|
417
|
-
│ ignored on │
|
|
418
|
-
│ Windows. │
|
|
419
|
-
│ [default: 120] │
|
|
420
|
-
│ --pycg-shard-str… <jedi|package> How --pycg-shard │
|
|
421
|
-
│ groups files │
|
|
422
|
-
│ (level 2 only). │
|
|
423
|
-
│ 'jedi' (default) │
|
|
424
|
-
│ partitions the │
|
|
425
|
-
│ Jedi │
|
|
426
|
-
│ module-dependen… │
|
|
427
|
-
│ graph (SCC + │
|
|
428
|
-
│ Louvain) so │
|
|
429
|
-
│ tightly-coupled │
|
|
430
|
-
│ modules │
|
|
431
|
-
│ co-compute and │
|
|
432
|
-
│ few call edges │
|
|
433
|
-
│ are severed │
|
|
434
|
-
│ between shards; │
|
|
435
|
-
│ import cycles │
|
|
436
|
-
│ are never split. │
|
|
437
|
-
│ 'package' uses │
|
|
438
|
-
│ the legacy │
|
|
439
|
-
│ one-shard-per-p… │
|
|
440
|
-
│ grouping. │
|
|
441
|
-
│ [default: jedi] │
|
|
442
|
-
│ --pycg-max-iter <int range> Cap on PyCG's │
|
|
443
|
-
│ [x>=-1] fixpoint passes │
|
|
444
|
-
│ per │
|
|
445
|
-
│ shard/project │
|
|
446
|
-
│ (level 2; │
|
|
447
|
-
│ default 50). │
|
|
448
|
-
│ PyCG iterates │
|
|
449
|
-
│ until its │
|
|
450
|
-
│ points-to state │
|
|
451
|
-
│ stops changing, │
|
|
452
|
-
│ but its │
|
|
453
|
-
│ access-path │
|
|
454
|
-
│ domain has no │
|
|
455
|
-
│ convergence │
|
|
456
|
-
│ bound, so heavy │
|
|
457
|
-
│ metaclass/mixin │
|
|
458
|
-
│ code (e.g. an │
|
|
459
|
-
│ ORM) can loop │
|
|
460
|
-
│ with each pass │
|
|
461
|
-
│ costing seconds. │
|
|
462
|
-
│ The cap returns │
|
|
463
|
-
│ a │
|
|
464
|
-
│ sound-but-incom… │
|
|
465
|
-
│ call graph │
|
|
466
|
-
│ instead of │
|
|
467
|
-
│ looping until │
|
|
468
|
-
│ the timeout │
|
|
469
|
-
│ kills it. Set to │
|
|
470
|
-
│ -1 for PyCG's │
|
|
471
|
-
│ unbounded │
|
|
472
|
-
│ run-to-converge… │
|
|
473
|
-
│ behaviour. │
|
|
474
|
-
│ [default: 50] │
|
|
370
|
+
│ artifact-text] │
|
|
371
|
+
│ --artifact-text-… <int range> Per-file byte │
|
|
372
|
+
│ [x>=1] cap on captured │
|
|
373
|
+
│ artifact │
|
|
374
|
+
│ `source`; a │
|
|
375
|
+
│ decodable file │
|
|
376
|
+
│ over the cap is │
|
|
377
|
+
│ truncated │
|
|
378
|
+
│ (text_truncated… │
|
|
379
|
+
│ sha256/size_byt… │
|
|
380
|
+
│ always reflect │
|
|
381
|
+
│ the full file. │
|
|
382
|
+
│ [default: │
|
|
383
|
+
│ 262144] │
|
|
475
384
|
│ --help Show this │
|
|
476
385
|
│ message and │
|
|
477
386
|
│ exit. │
|
|
@@ -493,13 +402,14 @@ $ canpy --help
|
|
|
493
402
|
canpy --input ./my-python-project --output ./out --format msgpack # → ./out/analysis.msgpack
|
|
494
403
|
```
|
|
495
404
|
|
|
496
|
-
3. **Enrich the call graph with
|
|
405
|
+
3. **Enrich the call graph with the defuse linker (level 2):**
|
|
497
406
|
```sh
|
|
498
407
|
canpy --input ./my-python-project -a 2
|
|
499
408
|
```
|
|
500
|
-
Level 1 edges come from Jedi's lexical resolution. `-a 2` runs **
|
|
501
|
-
|
|
502
|
-
|
|
409
|
+
Level 1 edges come from Jedi's lexical resolution. `-a 2` runs the **defuse linker** —
|
|
410
|
+
per-callable resolution over lexical scopes, import bindings, class hierarchies, and a
|
|
411
|
+
bounded type-propagation round — and merges its edges with Jedi's, backfilling the
|
|
412
|
+
callees Jedi could not resolve. Every edge is provenance-tagged (`jedi`, `defuse`).
|
|
503
413
|
|
|
504
414
|
4. **Emit a Neo4j snapshot, or push to a live database:**
|
|
505
415
|
```sh
|
|
@@ -528,7 +438,7 @@ $ canpy --help
|
|
|
528
438
|
canpy --input ./my-python-project -a 3 --graph-field-depth 2 # tighter access-path k-limit
|
|
529
439
|
```
|
|
530
440
|
Levels 3 and 4 also enrich the Neo4j projection (`--emit neo4j`) with the CPG overlay
|
|
531
|
-
(`:
|
|
441
|
+
(`:PyBodyNode` nodes wired by `PY_CFG_NEXT`/`PY_CDG`/`PY_DDG`, plus the level-4
|
|
532
442
|
`PY_PARAM_IN`/`PY_PARAM_OUT`/`PY_SUMMARY` edges — the cross-language dataflow vocabulary,
|
|
533
443
|
PY_-namespaced like every other row family so multi-language databases never mingle
|
|
534
444
|
analyzers' edges).
|
|
@@ -541,7 +451,7 @@ levels are cumulative and additive — `analysis.json(-a 1) ⊆ … ⊆ analysis
|
|
|
541
451
|
| Level | Flag | What it adds | Where it lands |
|
|
542
452
|
| --- | --- | --- | --- |
|
|
543
453
|
| **1** | `-a 1` (default) | Symbol table, Jedi call graph, and `call` nodes in each callable's `body` | `body` calls (`callee: null`) |
|
|
544
|
-
| **2** | `-a 2` |
|
|
454
|
+
| **2** | `-a 2` | Defuse-linker call-graph enrichment; each call's `callee` backfilled to a `can://` id | `call_graph`, `body` callees |
|
|
545
455
|
| **3** | `-a 3` | Native **intraprocedural** CFG/CDG/DDG (syntactic, name-equality, `prov: ["ssa"]`) | `cfg`, `cdg`, `ddg`, `@entry`/`@exit` on each callable |
|
|
546
456
|
| **4** | `-a 4` | **Interprocedural** SDG: synthetic param vertices, alias-aware DDG (`prov: ["points-to"]`) | `param_in`, `param_out`, `summary`, semantic `ddg` |
|
|
547
457
|
|
|
@@ -570,7 +480,7 @@ symbol-table signature by construction
|
|
|
570
480
|
external dependency to install; the analyzer falls back to the built-in `TypeBasedAliasOracle`
|
|
571
481
|
(Jedi-inferred types; unknown types conservatively alias) only when Scalpel can't resolve a
|
|
572
482
|
construct or a per-callable build fails, keeping the `may_alias` interface total. Call dispatch
|
|
573
|
-
comes from the merged Jedi
|
|
483
|
+
comes from the merged Jedi + defuse-linker call graph, treated as a frozen oracle.
|
|
574
484
|
- **Summaries:** relational formal-in → formal-out flows composed bottom-up over the Tarjan SCC
|
|
575
485
|
condensation of the call graph, a monotone fixpoint within SCCs; globals ride as extra formals,
|
|
576
486
|
closure captures bind at definition sites.
|
|
@@ -609,7 +519,7 @@ just populate more of the same tree:
|
|
|
609
519
|
}
|
|
610
520
|
},
|
|
611
521
|
"call_graph": [ { "src": "can://…/main(a)", "dst": "can://…/helper(x)",
|
|
612
|
-
"weight": 1, "prov": ["
|
|
522
|
+
"weight": 1, "prov": ["defuse", "jedi"] } ],
|
|
613
523
|
"external_symbols": { // imported/builtin call targets, keyed by id
|
|
614
524
|
"can://python/<app>/@external/os/getcwd":
|
|
615
525
|
{ "id": "can://python/<app>/@external/os/getcwd", "kind": "external",
|
|
@@ -642,14 +552,29 @@ A **callable** (function or method) carries its own CPG, keyed by node id:
|
|
|
642
552
|
}
|
|
643
553
|
```
|
|
644
554
|
|
|
555
|
+
The application envelope also contains three substrate sections:
|
|
556
|
+
|
|
557
|
+
- **`artifacts`** — discovered non-code files (manifests, configs, Docker files, CI workflows,
|
|
558
|
+
packaging files, scripts, docs, and legal files) with extraction status (`none`, `partial`, or
|
|
559
|
+
`full`; default `none`), keyed by relative path; each artifact carries the
|
|
560
|
+
`can://artifact/<app>/<path>` id namespace. Config files carry extracted `config_keys`
|
|
561
|
+
(keys, values, namespaces, and references) and `DEFINES_CONFIG` Neo4j edges.
|
|
562
|
+
- **`dependencies`** — declared packages with kind (`runtime`/`dev`/`optional`/`build`), spec,
|
|
563
|
+
locked version, and provenance (`prov`): where each binding came from (manifest file, lock file,
|
|
564
|
+
installed metadata).
|
|
565
|
+
- **`unresolved_imports`** — modules imported but not resolvable in the declared dependency set,
|
|
566
|
+
one entry per module.
|
|
567
|
+
|
|
645
568
|
Notable properties:
|
|
646
569
|
|
|
647
570
|
- **Durable `can://` ids** identify every node at callable granularity and above
|
|
648
571
|
(`can://python/<app>/<file>/<callable-sig>`); nodes below a callable use ordinal ids
|
|
649
572
|
(`@entry`, `@exit`, `line:col`, `@formal_in:N`, `line:col/actual_in:N`).
|
|
650
573
|
- **`source` lives once per module**; every node's text is the `module.source[span.bytes]` slice.
|
|
651
|
-
- **Cross-function edges** — `call_graph`, `param_in`, `param_out` — live at **application** scope;
|
|
574
|
+
- **Cross-function edges** — `call_graph`, `param_in`, `param_out`, `config_uses` — live at **application** scope;
|
|
652
575
|
the intraprocedural `cfg`/`cdg`/`ddg` and the `summary` edges live **on the callable**.
|
|
576
|
+
`config_uses` resolve from call-site key arguments to `ConfigKey` nodes; unresolved reads
|
|
577
|
+
go to `config_reads_unresolved`.
|
|
653
578
|
- **No dangling endpoints** — every `call_graph` `src`/`dst` joins the id space: declared
|
|
654
579
|
callables by their tree id, imported/builtin targets by a `…/@external/<module>/<name>` id
|
|
655
580
|
homed in `application.external_symbols`.
|
|
@@ -670,12 +595,16 @@ binary format).
|
|
|
670
595
|
|
|
671
596
|
### Neo4j graph
|
|
672
597
|
|
|
673
|
-
`--emit neo4j` projects the same schema v2.0.0 analysis into a labeled property graph. Every
|
|
674
|
-
label is `Py`-prefixed and every relationship type is
|
|
675
|
-
so multiple language analyzers can share one database
|
|
598
|
+
`--emit neo4j` projects the same schema v2.0.0 analysis into a labeled property graph. Every
|
|
599
|
+
Python-specific node label is `Py`-prefixed and every Python-specific relationship type is
|
|
600
|
+
`PY_`-prefixed (e.g. `:PyClass`, `PY_CALLS`) so multiple language analyzers can share one database
|
|
601
|
+
without label or relationship-type collisions. The one deliberate exception is the language-neutral
|
|
602
|
+
`Artifact`/`Package` subgraph (non-code files and third-party dependencies) — those nodes carry no
|
|
603
|
+
`Py` prefix, since they are meant as cross-language merge targets: a sibling-language analyzer over
|
|
604
|
+
the same repo should land on the same `Artifact`/`Package` nodes, not a per-language duplicate.
|
|
676
605
|
Declarations are keyed by their **`can://` id** under a shared `:PySymbol` label; calls, imports,
|
|
677
606
|
inheritance, decorators, and call sites are relationships. At `-a 3`/`-a 4` the projection gains the
|
|
678
|
-
**CPG overlay** — `:
|
|
607
|
+
**CPG overlay** — `:PyBodyNode` nodes (statements, and at level 4 the parameter vertices) wired by
|
|
679
608
|
`PY_CFG_NEXT`/`PY_CDG`/`PY_DDG`, plus the level-4 `PY_PARAM_IN`/`PY_PARAM_OUT`/`PY_SUMMARY` edges:
|
|
680
609
|
|
|
681
610
|
- **Without `--neo4j-uri`** — writes a self-contained `graph.cypher` (constraints + indexes, a scoped
|
|
@@ -733,6 +662,56 @@ runtime (Docker or Podman) and is enabled with an environment variable:
|
|
|
733
662
|
RUN_CONTAINER_TESTS=1 uv run pytest test/test_neo4j_bolt.py -s
|
|
734
663
|
```
|
|
735
664
|
|
|
665
|
+
## Graph query cookbook
|
|
666
|
+
|
|
667
|
+
Example Cypher over the projected graph (`--emit neo4j`, then load `graph.cypher` or push via Bolt).
|
|
668
|
+
|
|
669
|
+
```cypher
|
|
670
|
+
// who calls this function? (direct callers)
|
|
671
|
+
MATCH (c:PyCallable)-[:PY_CALLS]->(t:PyCallable {name: "process_payment"})
|
|
672
|
+
RETURN c.id
|
|
673
|
+
|
|
674
|
+
// every callable that reaches a given library, via the external ghosts
|
|
675
|
+
MATCH (c:PyCallable)-[:PY_CALLS]->(e:PyExternal)
|
|
676
|
+
WHERE e.id CONTAINS "/@external/requests/"
|
|
677
|
+
RETURN DISTINCT c.id
|
|
678
|
+
|
|
679
|
+
// entrypoints and the frameworks that invoke them
|
|
680
|
+
MATCH (m:PyCallable {is_entrypoint: true})
|
|
681
|
+
RETURN m.id, m.entrypoint_frameworks
|
|
682
|
+
|
|
683
|
+
// data dependences into one statement (level 3+)
|
|
684
|
+
MATCH (s:PyBodyNode {id: $stmt})<-[d:PY_DDG]-(src:PyBodyNode)
|
|
685
|
+
RETURN src.id, d.var, d.prov
|
|
686
|
+
|
|
687
|
+
// interprocedural flow through a parameter (level 4)
|
|
688
|
+
MATCH (a:PyBodyNode)-[:PY_PARAM_IN]->(f:PyBodyNode)
|
|
689
|
+
WHERE f.id STARTS WITH "can://python/myapp/src/api.py"
|
|
690
|
+
RETURN a.id, f.id
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Artifact and dependency queries (1.3.0+):
|
|
694
|
+
|
|
695
|
+
```cypher
|
|
696
|
+
// all container/orchestration configs in the app
|
|
697
|
+
MATCH (a:PyApplication)-[:HAS_ARTIFACT]->(f:Artifact)
|
|
698
|
+
WHERE any(r IN f.roles WHERE r IN ["service-topology", "container-image"])
|
|
699
|
+
RETURN f.id, f.format
|
|
700
|
+
|
|
701
|
+
// every callable that reaches code from a declared package
|
|
702
|
+
MATCH (c:PyCallable)-[:PY_CALLS]->(:PyExternal)<-[:PY_PROVIDES]-(p:Package {id: "pkg:pypi/requests"})
|
|
703
|
+
RETURN c.id
|
|
704
|
+
|
|
705
|
+
// undeclared imports (dependency hygiene)
|
|
706
|
+
MATCH (a:PyApplication)-[u:PY_UNRESOLVED_IMPORT]->(e:PyExternal)
|
|
707
|
+
WHERE NOT (e)<-[:PY_PROVIDES]-(:Package)
|
|
708
|
+
RETURN e.id, u.prov
|
|
709
|
+
|
|
710
|
+
// which lock file pins this package, and to what
|
|
711
|
+
MATCH (f:Artifact)-[l:LOCKS]->(p:Package {id: "pkg:pypi/numpy"})
|
|
712
|
+
RETURN f.id, l.version
|
|
713
|
+
```
|
|
714
|
+
|
|
736
715
|
## License
|
|
737
716
|
|
|
738
717
|
Apache 2.0 — see [LICENSE](./LICENSE).
|