codeanalyzer-python 0.2.0__py3-none-any.whl → 0.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.
Files changed (29) hide show
  1. codeanalyzer/__main__.py +99 -6
  2. codeanalyzer/core.py +192 -215
  3. codeanalyzer/neo4j/bolt.py +13 -2
  4. codeanalyzer/neo4j/catalog.py +2 -2
  5. codeanalyzer/neo4j/project.py +18 -10
  6. codeanalyzer/neo4j/rows.py +10 -5
  7. codeanalyzer/options/__init__.py +2 -2
  8. codeanalyzer/options/options.py +21 -1
  9. codeanalyzer/schema/__init__.py +2 -0
  10. codeanalyzer/schema/py_schema.py +16 -1
  11. codeanalyzer/semantic_analysis/call_graph.py +24 -3
  12. codeanalyzer/semantic_analysis/pycg/__init__.py +20 -0
  13. codeanalyzer/semantic_analysis/pycg/pycg_analysis.py +1054 -0
  14. codeanalyzer/semantic_analysis/{codeql/__init__.py → pycg/pycg_exceptions.py} +5 -8
  15. codeanalyzer/semantic_analysis/pycg/shard_planner.py +401 -0
  16. codeanalyzer/utils/logging.py +5 -2
  17. codeanalyzer/utils/progress_bar.py +15 -7
  18. codeanalyzer_python-0.3.0.dist-info/METADATA +550 -0
  19. codeanalyzer_python-0.3.0.dist-info/RECORD +38 -0
  20. codeanalyzer/semantic_analysis/codeql/codeql_analysis.py +0 -382
  21. codeanalyzer/semantic_analysis/codeql/codeql_exceptions.py +0 -12
  22. codeanalyzer/semantic_analysis/codeql/codeql_loader.py +0 -91
  23. codeanalyzer/semantic_analysis/codeql/codeql_query_runner.py +0 -185
  24. codeanalyzer_python-0.2.0.dist-info/METADATA +0 -393
  25. codeanalyzer_python-0.2.0.dist-info/RECORD +0 -39
  26. {codeanalyzer_python-0.2.0.dist-info → codeanalyzer_python-0.3.0.dist-info}/WHEEL +0 -0
  27. {codeanalyzer_python-0.2.0.dist-info → codeanalyzer_python-0.3.0.dist-info}/entry_points.txt +0 -0
  28. {codeanalyzer_python-0.2.0.dist-info → codeanalyzer_python-0.3.0.dist-info}/licenses/LICENSE +0 -0
  29. {codeanalyzer_python-0.2.0.dist-info → codeanalyzer_python-0.3.0.dist-info}/licenses/NOTICE +0 -0
codeanalyzer/__main__.py CHANGED
@@ -7,7 +7,7 @@ from codeanalyzer.core import Codeanalyzer
7
7
  from codeanalyzer.utils import _set_log_level, logger
8
8
  from codeanalyzer.config import OutputFormat
9
9
  from codeanalyzer.schema import model_dump_json
10
- from codeanalyzer.options import AnalysisOptions, EmitTarget
10
+ from codeanalyzer.options import AnalysisOptions, EmitTarget, ShardStrategy
11
11
 
12
12
 
13
13
  def main(
@@ -83,9 +83,16 @@ def main(
83
83
  help="Neo4j database name (default: server default). [env: NEO4J_DATABASE]",
84
84
  ),
85
85
  ] = None,
86
- using_codeql: Annotated[
87
- bool, typer.Option("--codeql/--no-codeql", help="Enable CodeQL-based analysis.")
88
- ] = False,
86
+ analysis_level: Annotated[
87
+ int,
88
+ typer.Option(
89
+ "-a",
90
+ "--analysis-level",
91
+ help="Analysis depth: 1=symbol table+Jedi call graph, 2=+PyCG call graph.",
92
+ min=1,
93
+ max=2,
94
+ ),
95
+ ] = 1,
89
96
  using_ray: Annotated[
90
97
  bool,
91
98
  typer.Option("--ray/--no-ray", help="Enable Ray for distributed analysis."),
@@ -104,6 +111,14 @@ def main(
104
111
  help="Skip test files in analysis.",
105
112
  ),
106
113
  ] = True,
114
+ no_venv: Annotated[
115
+ bool,
116
+ typer.Option(
117
+ "--no-venv/--venv",
118
+ help="Skip virtualenv creation and dependency installation; resolve "
119
+ "imports against the ambient Python environment instead.",
120
+ ),
121
+ ] = False,
107
122
  file_name: Annotated[
108
123
  Optional[Path],
109
124
  typer.Option(
@@ -129,6 +144,78 @@ def main(
129
144
  verbosity: Annotated[
130
145
  int, typer.Option("-v", count=True, help="Increase verbosity: -v, -vv, -vvv")
131
146
  ] = 0,
147
+ pycg_shard: Annotated[
148
+ bool,
149
+ typer.Option(
150
+ "--pycg-shard/--no-pycg-shard",
151
+ help=(
152
+ "Shard PyCG call-graph analysis by Python package (level 2 only). "
153
+ "When the project exceeds the 500-file ceiling, PyCG is run "
154
+ "independently per top-level package with cross-package imports "
155
+ "treated as ghost nodes. Without this flag, projects over the "
156
+ "ceiling fall back to Jedi-only edges."
157
+ ),
158
+ ),
159
+ ] = False,
160
+ pycg_shard_ceiling: Annotated[
161
+ int,
162
+ typer.Option(
163
+ "--pycg-shard-ceiling",
164
+ help=(
165
+ "Maximum files per shard when --pycg-shard is active (default 100). "
166
+ "Shards exceeding this limit are skipped; their call edges are "
167
+ "omitted from the call graph (Jedi edges for those packages are "
168
+ "still included). Lower values are safer for packages with deep "
169
+ "class hierarchies or heavy import graphs."
170
+ ),
171
+ min=1,
172
+ ),
173
+ ] = 100,
174
+ pycg_shard_timeout: Annotated[
175
+ int,
176
+ typer.Option(
177
+ "--pycg-shard-timeout",
178
+ help=(
179
+ "Per-shard wall-clock timeout in seconds when --pycg-shard is "
180
+ "active (default 120). A shard that exceeds this limit is skipped "
181
+ "gracefully. PyCG's fixpoint is bimodal: it either converges "
182
+ "quickly or diverges indefinitely, so the timeout acts as a final "
183
+ "safety net after the file-count ceiling. Set to 0 to disable. "
184
+ "POSIX only (macOS / Linux); ignored on Windows."
185
+ ),
186
+ min=0,
187
+ ),
188
+ ] = 120,
189
+ pycg_shard_strategy: Annotated[
190
+ ShardStrategy,
191
+ typer.Option(
192
+ "--pycg-shard-strategy",
193
+ help=(
194
+ "How --pycg-shard groups files (level 2 only). 'jedi' (default) "
195
+ "partitions the Jedi module-dependency graph (SCC + Louvain) so "
196
+ "tightly-coupled modules co-compute and few call edges are "
197
+ "severed between shards; import cycles are never split. "
198
+ "'package' uses the legacy one-shard-per-package-directory "
199
+ "grouping."
200
+ ),
201
+ ),
202
+ ] = ShardStrategy.JEDI,
203
+ pycg_max_iter: Annotated[
204
+ int,
205
+ typer.Option(
206
+ "--pycg-max-iter",
207
+ help=(
208
+ "Cap on PyCG's fixpoint passes per shard/project (level 2; "
209
+ "default 50). PyCG iterates until its points-to state stops "
210
+ "changing, but its access-path domain has no convergence bound, "
211
+ "so heavy metaclass/mixin code (e.g. an ORM) can loop with each "
212
+ "pass costing seconds. The cap returns a sound-but-incomplete "
213
+ "call graph instead of looping until the timeout kills it. "
214
+ "Set to -1 for PyCG's unbounded run-to-convergence behaviour."
215
+ ),
216
+ min=-1,
217
+ ),
218
+ ] = 50,
132
219
  ):
133
220
  options = AnalysisOptions(
134
221
  input=input,
@@ -140,14 +227,20 @@ def main(
140
227
  neo4j_user=neo4j_user,
141
228
  neo4j_password=neo4j_password,
142
229
  neo4j_database=neo4j_database,
143
- using_codeql=using_codeql,
230
+ analysis_level=analysis_level,
144
231
  using_ray=using_ray,
145
232
  rebuild_analysis=rebuild_analysis,
146
233
  skip_tests=skip_tests,
234
+ no_venv=no_venv,
147
235
  file_name=file_name,
148
236
  cache_dir=cache_dir,
149
237
  clear_cache=clear_cache,
150
238
  verbosity=verbosity,
239
+ pycg_shard=pycg_shard,
240
+ pycg_shard_ceiling=pycg_shard_ceiling,
241
+ pycg_shard_timeout=pycg_shard_timeout,
242
+ pycg_shard_strategy=pycg_shard_strategy,
243
+ pycg_max_iter=pycg_max_iter,
151
244
  )
152
245
 
153
246
  _set_log_level(options.verbosity)
@@ -221,7 +314,7 @@ def _write_output(artifacts, output_dir: Path, format: OutputFormat):
221
314
  app = typer.Typer(
222
315
  callback=main,
223
316
  name="canpy",
224
- help="Static Analysis on Python source code using Jedi, CodeQL and Tree sitter.",
317
+ help="Static Analysis on Python source code using Jedi, PyCG and Tree sitter.",
225
318
  invoke_without_command=True,
226
319
  no_args_is_help=True,
227
320
  add_completion=False,