codeanalyzer-python 0.2.1__py3-none-any.whl → 0.3.1__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 (32) hide show
  1. codeanalyzer/__main__.py +116 -6
  2. codeanalyzer/core.py +139 -213
  3. codeanalyzer/neo4j/__init__.py +1 -1
  4. codeanalyzer/neo4j/emit.py +2 -2
  5. codeanalyzer/neo4j/project.py +84 -18
  6. codeanalyzer/neo4j/schema.py +261 -15
  7. codeanalyzer/options/__init__.py +2 -2
  8. codeanalyzer/options/options.py +20 -1
  9. codeanalyzer/provenance.py +61 -0
  10. codeanalyzer/schema/py_schema.py +39 -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/syntactic_analysis/import_resolver.py +67 -0
  17. codeanalyzer/syntactic_analysis/symbol_table_builder.py +74 -10
  18. codeanalyzer/utils/logging.py +5 -2
  19. codeanalyzer/utils/progress_bar.py +15 -7
  20. codeanalyzer_python-0.3.1.dist-info/METADATA +553 -0
  21. codeanalyzer_python-0.3.1.dist-info/RECORD +39 -0
  22. {codeanalyzer_python-0.2.1.dist-info → codeanalyzer_python-0.3.1.dist-info}/WHEEL +1 -1
  23. codeanalyzer/neo4j/catalog.py +0 -245
  24. codeanalyzer/semantic_analysis/codeql/codeql_analysis.py +0 -382
  25. codeanalyzer/semantic_analysis/codeql/codeql_exceptions.py +0 -12
  26. codeanalyzer/semantic_analysis/codeql/codeql_loader.py +0 -91
  27. codeanalyzer/semantic_analysis/codeql/codeql_query_runner.py +0 -185
  28. codeanalyzer_python-0.2.1.dist-info/METADATA +0 -415
  29. codeanalyzer_python-0.2.1.dist-info/RECORD +0 -39
  30. {codeanalyzer_python-0.2.1.dist-info → codeanalyzer_python-0.3.1.dist-info}/entry_points.txt +0 -0
  31. {codeanalyzer_python-0.2.1.dist-info → codeanalyzer_python-0.3.1.dist-info}/licenses/LICENSE +0 -0
  32. {codeanalyzer_python-0.2.1.dist-info → codeanalyzer_python-0.3.1.dist-info}/licenses/NOTICE +0 -0
@@ -176,6 +176,7 @@ class PyImport(BaseModel):
176
176
  module: str
177
177
  name: str
178
178
  alias: Optional[str] = None
179
+ resolved_module: Optional[str] = None
179
180
  start_line: int = -1
180
181
  end_line: int = -1
181
182
  start_column: int = -1
@@ -240,6 +241,19 @@ class PyCallableParameter(BaseModel):
240
241
  end_column: int = -1
241
242
 
242
243
 
244
+ @builder
245
+ @msgpk
246
+ class PyCallArgument(BaseModel):
247
+ """One call-site argument: AST category + inferred type, kept separate.
248
+
249
+ The legacy ``PyCallsite.argument_types`` mixed these two vocabularies
250
+ in one list; this model is the disambiguated replacement (#86).
251
+ """
252
+
253
+ ast_kind: str
254
+ inferred_type: Optional[str] = None
255
+
256
+
243
257
  @builder
244
258
  @msgpk
245
259
  class PyCallsite(BaseModel):
@@ -249,6 +263,7 @@ class PyCallsite(BaseModel):
249
263
  receiver_expr: Optional[str] = None
250
264
  receiver_type: Optional[str] = None
251
265
  argument_types: List[str] = []
266
+ arguments: List[PyCallArgument] = []
252
267
  return_type: Optional[str] = None
253
268
  callee_signature: Optional[str] = None
254
269
  is_constructor_call: bool = False
@@ -295,6 +310,7 @@ class PyClassAttribute(BaseModel):
295
310
 
296
311
  name: str
297
312
  type: Optional[str] = None
313
+ initializer: Optional[str] = None
298
314
  comments: List[PyComment] = []
299
315
  start_line: int = -1
300
316
  end_line: int = -1
@@ -355,7 +371,7 @@ class PyCallEdge(BaseModel):
355
371
  target: str # callee's PyCallable.signature
356
372
  type: Literal["CALL_DEP"] = "CALL_DEP"
357
373
  weight: int = 1
358
- provenance: List[Literal["jedi", "codeql", "joern"]] = []
374
+ provenance: List[Literal["jedi", "pycg", "joern"]] = []
359
375
 
360
376
 
361
377
  @builder
@@ -369,6 +385,26 @@ class PyExternalSymbol(BaseModel):
369
385
  module: Optional[str] = None # best-effort owning module, e.g. "requests"
370
386
 
371
387
 
388
+ @builder
389
+ @msgpk
390
+ class PyRepositoryInfo(BaseModel):
391
+ """Where the analyzed source came from: git provenance captured at analysis time."""
392
+
393
+ uri: Optional[str] = None
394
+ revision: str
395
+ dirty: bool = False
396
+
397
+
398
+ @builder
399
+ @msgpk
400
+ class PyAnalyzerInfo(BaseModel):
401
+ """Which analyzer produced this snapshot, and how it was configured."""
402
+
403
+ name: str
404
+ version: str
405
+ config: Dict[str, Any] = {}
406
+
407
+
372
408
  @builder
373
409
  @msgpk
374
410
  class PyApplication(BaseModel):
@@ -380,3 +416,5 @@ class PyApplication(BaseModel):
380
416
  # builtin members), keyed by signature. Populated by the analyzer so every
381
417
  # backend (JSON and Neo4j) shares one authoritative external-symbol set.
382
418
  external_symbols: Dict[str, PyExternalSymbol] = {}
419
+ analyzer: Optional[PyAnalyzerInfo] = None
420
+ repository: Optional[PyRepositoryInfo] = None
@@ -173,7 +173,7 @@ def jedi_call_graph_edges(
173
173
 
174
174
  Edges are coalesced on ``(source, target)``: ``weight`` is the count of
175
175
  matching sites. Provenance is always ``["jedi"]``; combine with
176
- CodeQL-derived edges via ``merge_edges``.
176
+ PyCG-derived edges via ``merge_edges``.
177
177
  """
178
178
  counts: Counter = Counter()
179
179
  for caller in iter_callables_in_symbol_table(symbol_table):
@@ -191,7 +191,7 @@ def jedi_call_graph_edges(
191
191
  def resolve_unresolved_constructors(symbol_table: Dict[str, PyModule]) -> int:
192
192
  """Fill in ``PyCallsite.callee_signature`` for unresolved constructor sites.
193
193
 
194
- When both Jedi and CodeQL fail to resolve a constructor call (commonly
194
+ When Jedi fails to resolve a constructor call (commonly
195
195
  for classes nested inside functions or methods, where static-analysis
196
196
  points-to is weakest), Jedi still flags the site as
197
197
  ``is_constructor_call=True`` with ``method_name`` set to the class's
@@ -246,12 +246,33 @@ def resolve_unresolved_constructors(symbol_table: Dict[str, PyModule]) -> int:
246
246
  return resolved
247
247
 
248
248
 
249
+ def filter_external_edges(
250
+ edges: List[PyCallEdge],
251
+ symbol_table: Dict[str, PyModule],
252
+ ) -> List[PyCallEdge]:
253
+ """Remove edges where both source and target are outside the app namespace.
254
+
255
+ Edges where an app callable calls a library function (or vice-versa) are
256
+ retained; only lib→lib edges are dropped. The app symbol set is built by
257
+ walking every callable in the symbol table recursively (including nested
258
+ functions and closures via ``inner_callables``) plus every class, so
259
+ PyCG-discovered closure nodes are correctly recognised as app symbols.
260
+ """
261
+ app_symbols: set = {c.signature for c in iter_callables_in_symbol_table(symbol_table)}
262
+ app_symbols.update(cls.signature for cls in iter_classes_in_symbol_table(symbol_table))
263
+
264
+ return [
265
+ e for e in edges
266
+ if e.source in app_symbols or e.target in app_symbols
267
+ ]
268
+
269
+
249
270
  def merge_edges(*edge_lists: list) -> list:
250
271
  """Merge multiple ``List[PyCallEdge]`` into one.
251
272
 
252
273
  Edges with the same ``(source, target)`` are coalesced: weights sum,
253
274
  provenance is the sorted union. Useful for combining edges produced
254
- by different backends (e.g. Jedi + CodeQL).
275
+ by different backends (e.g. Jedi + PyCG).
255
276
  """
256
277
  by_key: Dict[Tuple[str, str], PyCallEdge] = {}
257
278
  for edges in edge_lists:
@@ -0,0 +1,20 @@
1
+ ################################################################################
2
+ # Copyright IBM Corporation 2025
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ################################################################################
16
+
17
+ from codeanalyzer.semantic_analysis.pycg.pycg_analysis import PyCG
18
+ from codeanalyzer.semantic_analysis.pycg.pycg_exceptions import PyCGExceptions
19
+
20
+ __all__ = ["PyCG", "PyCGExceptions"]