codeanalyzer-python 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
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/__main__.py +29 -0
- codeanalyzer/artifacts/__init__.py +20 -0
- codeanalyzer/artifacts/config_keys.py +588 -0
- codeanalyzer/artifacts/config_use.py +597 -0
- codeanalyzer/artifacts/config_use_rules.yml +58 -0
- codeanalyzer/artifacts/dependencies.py +237 -0
- codeanalyzer/artifacts/discovery.py +167 -0
- codeanalyzer/artifacts/parsers.py +248 -0
- codeanalyzer/core.py +91 -0
- codeanalyzer/dataflow/builder.py +15 -1
- codeanalyzer/neo4j/project.py +192 -0
- codeanalyzer/neo4j/schema.py +57 -0
- codeanalyzer/options/options.py +5 -0
- codeanalyzer/schema/ids.py +21 -0
- codeanalyzer/schema/py_schema.py +118 -0
- codeanalyzer/syntactic_analysis/symbol_table_builder.py +11 -0
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/METADATA +109 -5
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/RECORD +22 -15
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/WHEEL +0 -0
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/entry_points.txt +0 -0
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
|
@@ -21,6 +21,7 @@ Requires-Dist: ray==2.0.0; python_version < '3.11'
|
|
|
21
21
|
Requires-Dist: requests<3.0.0,>=2.20.0; python_version >= '3.11'
|
|
22
22
|
Requires-Dist: rich<14.0.0,>=12.6.0; python_version < '3.11'
|
|
23
23
|
Requires-Dist: rich<15.0.0,>=14.0.0; python_version >= '3.11'
|
|
24
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
24
25
|
Requires-Dist: typer<1.0.0,>=0.9.0; python_version < '3.11'
|
|
25
26
|
Requires-Dist: typer<2.0.0,>=0.9.0; python_version >= '3.11'
|
|
26
27
|
Requires-Dist: typing-extensions<5.0.0,>=4.0.0; python_version < '3.11'
|
|
@@ -315,6 +316,16 @@ $ canpy --help
|
|
|
315
316
|
│ environment │
|
|
316
317
|
│ instead. │
|
|
317
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… │
|
|
318
329
|
│ --file-name <path> Analyze only the │
|
|
319
330
|
│ specified file │
|
|
320
331
|
│ (relative to │
|
|
@@ -346,6 +357,30 @@ $ canpy --help
|
|
|
346
357
|
│ shipped rules. A │
|
|
347
358
|
│ malformed file │
|
|
348
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). │
|
|
369
|
+
│ [default: │
|
|
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] │
|
|
349
384
|
│ --help Show this │
|
|
350
385
|
│ message and │
|
|
351
386
|
│ exit. │
|
|
@@ -517,14 +552,29 @@ A **callable** (function or method) carries its own CPG, keyed by node id:
|
|
|
517
552
|
}
|
|
518
553
|
```
|
|
519
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
|
+
|
|
520
568
|
Notable properties:
|
|
521
569
|
|
|
522
570
|
- **Durable `can://` ids** identify every node at callable granularity and above
|
|
523
571
|
(`can://python/<app>/<file>/<callable-sig>`); nodes below a callable use ordinal ids
|
|
524
572
|
(`@entry`, `@exit`, `line:col`, `@formal_in:N`, `line:col/actual_in:N`).
|
|
525
573
|
- **`source` lives once per module**; every node's text is the `module.source[span.bytes]` slice.
|
|
526
|
-
- **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;
|
|
527
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`.
|
|
528
578
|
- **No dangling endpoints** — every `call_graph` `src`/`dst` joins the id space: declared
|
|
529
579
|
callables by their tree id, imported/builtin targets by a `…/@external/<module>/<name>` id
|
|
530
580
|
homed in `application.external_symbols`.
|
|
@@ -545,9 +595,13 @@ binary format).
|
|
|
545
595
|
|
|
546
596
|
### Neo4j graph
|
|
547
597
|
|
|
548
|
-
`--emit neo4j` projects the same schema v2.0.0 analysis into a labeled property graph. Every
|
|
549
|
-
label is `Py`-prefixed and every relationship type is
|
|
550
|
-
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.
|
|
551
605
|
Declarations are keyed by their **`can://` id** under a shared `:PySymbol` label; calls, imports,
|
|
552
606
|
inheritance, decorators, and call sites are relationships. At `-a 3`/`-a 4` the projection gains the
|
|
553
607
|
**CPG overlay** — `:PyBodyNode` nodes (statements, and at level 4 the parameter vertices) wired by
|
|
@@ -608,6 +662,56 @@ runtime (Docker or Podman) and is enabled with an environment variable:
|
|
|
608
662
|
RUN_CONTAINER_TESTS=1 uv run pytest test/test_neo4j_bolt.py -s
|
|
609
663
|
```
|
|
610
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
|
+
|
|
611
715
|
## License
|
|
612
716
|
|
|
613
717
|
Apache 2.0 — see [LICENSE](./LICENSE).
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
codeanalyzer/__init__.py,sha256=BZ3Kuwl-F_F-8H8cepLnVJ4Ku4NNUjjqg0Y6ujPQSsI,108
|
|
2
|
-
codeanalyzer/__main__.py,sha256=
|
|
3
|
-
codeanalyzer/core.py,sha256=
|
|
2
|
+
codeanalyzer/__main__.py,sha256=VVGmkwLGkcfCdbikp2FdXzpGZEA1md8n8WhNSxJWz1E,16080
|
|
3
|
+
codeanalyzer/core.py,sha256=yJ14_jL2V4qDD6pEMEltfeOLcFYTMRkRnh0dyP6RVR4,46715
|
|
4
4
|
codeanalyzer/provenance.py,sha256=DT-DqwdVwO7g-GyK3sC0_4vDNCUjZYEY1fyYCt-7VRM,2306
|
|
5
5
|
codeanalyzer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
codeanalyzer/artifacts/__init__.py,sha256=317sEeZLS1AYsfYDYIk7tLR3AdDFwWw_cmkeZDabkJo,906
|
|
7
|
+
codeanalyzer/artifacts/config_keys.py,sha256=ovwptAErLYutlCzUwvyim2kH6-6vUKxuRcBV2KJS71Y,26843
|
|
8
|
+
codeanalyzer/artifacts/config_use.py,sha256=1-jGacW249dwF7b6DOrTFuJ7sA5Ho9khgrgZqND0nro,27173
|
|
9
|
+
codeanalyzer/artifacts/config_use_rules.yml,sha256=fNgI6IceOsj78LYmgoDH-vQ4-Mfb2ZFGKIQhLuoL4ik,1483
|
|
10
|
+
codeanalyzer/artifacts/dependencies.py,sha256=bURMUbBzHaF8bInRk-INp1GaGujsUSXnFqLtNyECUMI,10888
|
|
11
|
+
codeanalyzer/artifacts/discovery.py,sha256=8as4RD5oNgZM2qyg19HUqsdq9eETwPDGQUOn4fZLOsQ,7427
|
|
12
|
+
codeanalyzer/artifacts/parsers.py,sha256=xOjC53tT0Mv0k7ONGwzl5XUvpBLnZlEcGmyrX5DS45k,9035
|
|
6
13
|
codeanalyzer/dataflow/__init__.py,sha256=HCNOH24rAGLUMG94N9aC-2SsAWSntk3uys7e39ipU5o,1725
|
|
7
14
|
codeanalyzer/dataflow/access_paths.py,sha256=wC8Q9qD-RZzkoFWMVvu_6uNNmYP8z48OGp9h9v3F1d4,23623
|
|
8
15
|
codeanalyzer/dataflow/alias.py,sha256=ZYKY0DiR3GHv6YJr7C001Lode9rZsk6-gxdomUyIvwg,3928
|
|
9
|
-
codeanalyzer/dataflow/builder.py,sha256=
|
|
16
|
+
codeanalyzer/dataflow/builder.py,sha256=OOj5J-O0WgBnLMSu6hhixkM8dVkUopwBR-wlQq0INhE,32463
|
|
10
17
|
codeanalyzer/dataflow/cfg.py,sha256=u5YXJjAaDshdgv-9kWWITbBJFP-MwXKq6hbNh0fhDm8,25307
|
|
11
18
|
codeanalyzer/dataflow/defuse.py,sha256=LrX1ToOZzR79NlAGg5T647UAFkpiEqEnT7t0K5RXOiI,4377
|
|
12
19
|
codeanalyzer/dataflow/dominance.py,sha256=X7Ki1QRdVqaseYsJtiUL7m9MbcC2WHEv9b8bczSLFUA,5086
|
|
@@ -41,31 +48,31 @@ codeanalyzer/neo4j/__init__.py,sha256=8Ei6uThTOqmul6GhnKoGOjLXJZbyDwF5Jo-8I8NxnA
|
|
|
41
48
|
codeanalyzer/neo4j/bolt.py,sha256=qEBtQlBjaMPpLQPUCOAR9jEqLxpXYJG5mtxdYAhjtMI,10134
|
|
42
49
|
codeanalyzer/neo4j/cypher.py,sha256=a7YSmxxDbPcYc5gI4TW9sM6_WOE3RgLXTGBR9K8W8kw,5321
|
|
43
50
|
codeanalyzer/neo4j/emit.py,sha256=QdrZWG3_IQHMcKCX4bFINpXvqZU2Qfsi9beIJovC2p4,3493
|
|
44
|
-
codeanalyzer/neo4j/project.py,sha256=
|
|
51
|
+
codeanalyzer/neo4j/project.py,sha256=DcymijKIy9TvBUhzlB_0fDmoSA8WRufJU-1oVP1hH1w,35102
|
|
45
52
|
codeanalyzer/neo4j/rows.py,sha256=med0XFa63PPUVUhJMq1TtVcJxgaVW39sgxBRZJDIJ_w,7811
|
|
46
|
-
codeanalyzer/neo4j/schema.py,sha256=
|
|
53
|
+
codeanalyzer/neo4j/schema.py,sha256=HXM-rG-NBuujXU2fXGAUb65sGRKKTXDGB7jr360qEYQ,14330
|
|
47
54
|
codeanalyzer/options/__init__.py,sha256=6NewN0a-1HAEgKcxQjYyVn2WEDLrhax8h3WLgZddeqI,94
|
|
48
|
-
codeanalyzer/options/options.py,sha256=
|
|
55
|
+
codeanalyzer/options/options.py,sha256=3GF1m4AI-9NN2sWmhl8wrvi2HjMwofec-We3WqKs_HA,1642
|
|
49
56
|
codeanalyzer/schema/__init__.py,sha256=hIyz02abUJNPW8yUgguaeKflZjTmOZWbDiKICNBD7yk,4141
|
|
50
57
|
codeanalyzer/schema/assign_ids.py,sha256=pQHluLaO5a6hFREp3vjVRsUeAfTYSJ95RXS9TZvnoys,1590
|
|
51
58
|
codeanalyzer/schema/call_graph_ids.py,sha256=mWAhJDBsW8i-siJiINOHCXEm4qM6F_5se9-HuHWIFqU,568
|
|
52
|
-
codeanalyzer/schema/ids.py,sha256=
|
|
59
|
+
codeanalyzer/schema/ids.py,sha256=aOzsgVaOo6x72dnRgNxlnt6b5wbZzhTm0JOz6pdI57I,1672
|
|
53
60
|
codeanalyzer/schema/l1_body.py,sha256=5Su347kwAPNflJDf7SvBR3sNXx9PVdGEml2pOpQCwo0,1684
|
|
54
61
|
codeanalyzer/schema/l2_callees.py,sha256=CTcBeGoO04DstDC6h-1sHMbbIrHDEOMxP01U9VLBcfc,2327
|
|
55
|
-
codeanalyzer/schema/py_schema.py,sha256=
|
|
62
|
+
codeanalyzer/schema/py_schema.py,sha256=pN03WJcb96LegGSFs7HIBWzTE8bnn8T2adH9M9FJGgQ,23966
|
|
56
63
|
codeanalyzer/semantic_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
64
|
codeanalyzer/semantic_analysis/call_graph.py,sha256=6YEB_wTn5-oQYLrbIhJYE0BsHl4fpWPoy5Hwd9mTnGc,11918
|
|
58
65
|
codeanalyzer/semantic_analysis/defuse_linker.py,sha256=bSn1rCun28OQqSd2NOc9aVBSm47Gt9Iul4r_gwfqO1w,64930
|
|
59
66
|
codeanalyzer/syntactic_analysis/__init__.py,sha256=EUQkJEh6wHjWx2qTTKbTbUgwSbfKeNieKHNy7RknVXA,476
|
|
60
67
|
codeanalyzer/syntactic_analysis/exceptions.py,sha256=whs_n0vIu655Jkk1a7iOoXY6iIca4pZqJnU40V9Ejaw,537
|
|
61
68
|
codeanalyzer/syntactic_analysis/import_resolver.py,sha256=Q8noZwSdDNt4P4NMqDTB9FaS-M0_d4ASK9GnJop9MDI,2751
|
|
62
|
-
codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=
|
|
69
|
+
codeanalyzer/syntactic_analysis/symbol_table_builder.py,sha256=eTknBDlUuuQd3JEwbRtJt5pGVU89kOnzfmT4D5J_GKQ,47917
|
|
63
70
|
codeanalyzer/utils/__init__.py,sha256=hC6VWdR5rerSqBxzu9KQHTASWqwrrYJv-CMDwrTlzkc,137
|
|
64
71
|
codeanalyzer/utils/logging.py,sha256=Fw0tattPAOMs3o0JMjjXhRVLIF64f-SCcygUXF9jqeg,904
|
|
65
72
|
codeanalyzer/utils/progress_bar.py,sha256=C9JtzVdd10lIxTv-KA6PebqjKWueC_vMGwVzAtHuHIw,2818
|
|
66
|
-
codeanalyzer_python-1.
|
|
67
|
-
codeanalyzer_python-1.
|
|
68
|
-
codeanalyzer_python-1.
|
|
69
|
-
codeanalyzer_python-1.
|
|
70
|
-
codeanalyzer_python-1.
|
|
71
|
-
codeanalyzer_python-1.
|
|
73
|
+
codeanalyzer_python-1.3.0.dist-info/METADATA,sha256=QAUQ1DK8997qsSI8-g4d294RfI7wLPljdOL4gi42Ofo,42305
|
|
74
|
+
codeanalyzer_python-1.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
75
|
+
codeanalyzer_python-1.3.0.dist-info/entry_points.txt,sha256=v4Vux0Nnx7sOntVk_CH7W9RX6SkIkvR1FQYq73oVlCQ,105
|
|
76
|
+
codeanalyzer_python-1.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
77
|
+
codeanalyzer_python-1.3.0.dist-info/licenses/NOTICE,sha256=MdVkNYqHJ20on2FmWvgD4WpMsX5mir33xdyPvTXd0A0,1223
|
|
78
|
+
codeanalyzer_python-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
{codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{codeanalyzer_python-1.2.0.dist-info → codeanalyzer_python-1.3.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|