codeanalyzer-python 0.1.10__tar.gz → 0.1.12__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-0.1.10 → codeanalyzer_python-0.1.12}/PKG-INFO +24 -12
- codeanalyzer_python-0.1.12/codeanalyzer/__main__.py +100 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/core.py +17 -31
- codeanalyzer_python-0.1.12/codeanalyzer/options/__init__.py +3 -0
- codeanalyzer_python-0.1.12/codeanalyzer/options/options.py +25 -0
- codeanalyzer_python-0.1.12/codeanalyzer/schema/__init__.py +72 -0
- codeanalyzer_python-0.1.12/pyproject.toml +115 -0
- codeanalyzer_python-0.1.10/codeanalyzer/__main__.py +0 -145
- codeanalyzer_python-0.1.10/codeanalyzer/schema/__init__.py +0 -33
- codeanalyzer_python-0.1.10/pyproject.toml +0 -92
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/.gitignore +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/LICENSE +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/NOTICE +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/README.md +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/config/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/config/config.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/jedi.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/py.typed +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/schema/py_schema.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_analysis.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_exceptions.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_loader.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_query_runner.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/wala/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/exceptions.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/symbol_table_builder.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/__init__.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/logging.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/progress_bar.py +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/LICENSE +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/README.md +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/properties/README.md +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/LICENSE +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/README.md +0 -0
- {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/docs/README.md +0 -0
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeanalyzer-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.12
|
|
4
4
|
Summary: Static Analysis on Python source code using Jedi, CodeQL and Treesitter.
|
|
5
5
|
Author-email: Rahul Krishna <i.m.ralk@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
License-File: NOTICE
|
|
8
8
|
Requires-Python: >=3.9
|
|
9
|
-
Requires-Dist: jedi<0.20.0,>=0.18.0
|
|
10
|
-
Requires-Dist:
|
|
11
|
-
Requires-Dist:
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist:
|
|
9
|
+
Requires-Dist: jedi<0.20.0,>=0.18.0; python_version < '3.11'
|
|
10
|
+
Requires-Dist: jedi<=0.19.2; python_version >= '3.11'
|
|
11
|
+
Requires-Dist: msgpack<1.0.7,>=1.0.0; python_version < '3.11'
|
|
12
|
+
Requires-Dist: msgpack<2.0.0,>=1.0.7; python_version >= '3.11'
|
|
13
|
+
Requires-Dist: networkx<3.2.0,>=2.6.0; python_version < '3.11'
|
|
14
|
+
Requires-Dist: networkx<4.0.0,>=3.0.0; python_version >= '3.11'
|
|
15
|
+
Requires-Dist: numpy<1.24.0,>=1.21.0; python_version < '3.11'
|
|
16
|
+
Requires-Dist: numpy<2.0.0,>=1.24.0; python_version >= '3.11' and python_version < '3.12'
|
|
17
|
+
Requires-Dist: numpy<2.0.0,>=1.26.0; python_version >= '3.12'
|
|
18
|
+
Requires-Dist: packaging>=25.0
|
|
19
|
+
Requires-Dist: pandas<2.0.0,>=1.3.0; python_version < '3.11'
|
|
20
|
+
Requires-Dist: pandas<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
21
|
+
Requires-Dist: pydantic<2.0.0,>=1.8.0; python_version < '3.11'
|
|
22
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0; python_version >= '3.11'
|
|
23
|
+
Requires-Dist: ray<3.0.0,>=2.10.0; python_version >= '3.11'
|
|
24
|
+
Requires-Dist: ray==2.0.0; python_version < '3.11'
|
|
25
|
+
Requires-Dist: requests<3.0.0,>=2.20.0; python_version >= '3.11'
|
|
26
|
+
Requires-Dist: rich<14.0.0,>=12.6.0; python_version < '3.11'
|
|
27
|
+
Requires-Dist: rich<15.0.0,>=14.0.0; python_version >= '3.11'
|
|
28
|
+
Requires-Dist: typer<1.0.0,>=0.9.0; python_version < '3.11'
|
|
29
|
+
Requires-Dist: typer<2.0.0,>=0.9.0; python_version >= '3.11'
|
|
30
|
+
Requires-Dist: typing-extensions<5.0.0,>=4.0.0; python_version < '3.11'
|
|
31
|
+
Requires-Dist: typing-extensions<6.0.0,>=4.5.0; python_version >= '3.11'
|
|
20
32
|
Description-Content-Type: text/markdown
|
|
21
33
|
|
|
22
34
|

|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional, Annotated
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from codeanalyzer.core import Codeanalyzer
|
|
7
|
+
from codeanalyzer.utils import _set_log_level, logger
|
|
8
|
+
from codeanalyzer.config import OutputFormat
|
|
9
|
+
from codeanalyzer.schema import model_dump_json
|
|
10
|
+
from codeanalyzer.options import AnalysisOptions
|
|
11
|
+
|
|
12
|
+
def main(
|
|
13
|
+
input: Annotated[Path, typer.Option("-i", "--input", help="Path to the project root directory.")],
|
|
14
|
+
output: Optional[Path] = typer.Option(None, "-o", "--output"),
|
|
15
|
+
format: OutputFormat = typer.Option(OutputFormat.JSON, "-f", "--format"),
|
|
16
|
+
analysis_level: int = typer.Option(1, "-a", "--analysis-level"),
|
|
17
|
+
using_codeql: bool = typer.Option(False, "--codeql/--no-codeql"),
|
|
18
|
+
using_ray: bool = typer.Option(False, "--ray/--no-ray"),
|
|
19
|
+
rebuild_analysis: bool = typer.Option(False, "--eager/--lazy"),
|
|
20
|
+
skip_tests: bool = typer.Option(True, "--skip-tests/--include-tests"),
|
|
21
|
+
file_name: Optional[Path] = typer.Option(None, "--file-name"),
|
|
22
|
+
cache_dir: Optional[Path] = typer.Option(None, "-c", "--cache-dir"),
|
|
23
|
+
clear_cache: bool = typer.Option(False, "--clear-cache/--keep-cache"),
|
|
24
|
+
verbosity: int = typer.Option(0, "-v", count=True),
|
|
25
|
+
):
|
|
26
|
+
options = AnalysisOptions(
|
|
27
|
+
input=input,
|
|
28
|
+
output=output,
|
|
29
|
+
format=format,
|
|
30
|
+
analysis_level=analysis_level,
|
|
31
|
+
using_codeql=using_codeql,
|
|
32
|
+
using_ray=using_ray,
|
|
33
|
+
rebuild_analysis=rebuild_analysis,
|
|
34
|
+
skip_tests=skip_tests,
|
|
35
|
+
file_name=file_name,
|
|
36
|
+
cache_dir=cache_dir,
|
|
37
|
+
clear_cache=clear_cache,
|
|
38
|
+
verbosity=verbosity,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_set_log_level(options.verbosity)
|
|
42
|
+
if not options.input.exists():
|
|
43
|
+
logger.error(f"Input path '{options.input}' does not exist.")
|
|
44
|
+
raise typer.Exit(code=1)
|
|
45
|
+
|
|
46
|
+
if options.file_name is not None:
|
|
47
|
+
full_file_path = options.input / options.file_name
|
|
48
|
+
if not full_file_path.exists():
|
|
49
|
+
logger.error(f"Specified file '{options.file_name}' does not exist in '{options.input}'.")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
if not full_file_path.is_file():
|
|
52
|
+
logger.error(f"Specified path '{options.file_name}' is not a file.")
|
|
53
|
+
raise typer.Exit(code=1)
|
|
54
|
+
if not str(options.file_name).endswith('.py'):
|
|
55
|
+
logger.error(f"Specified file '{options.file_name}' is not a Python file (.py).")
|
|
56
|
+
raise typer.Exit(code=1)
|
|
57
|
+
|
|
58
|
+
with Codeanalyzer(options) as analyzer:
|
|
59
|
+
artifacts = analyzer.analyze()
|
|
60
|
+
|
|
61
|
+
if options.output is None:
|
|
62
|
+
print(model_dump_json(artifacts, separators=(",", ":")))
|
|
63
|
+
else:
|
|
64
|
+
options.output.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
_write_output(artifacts, options.output, options.format)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _write_output(artifacts, output_dir: Path, format: OutputFormat):
|
|
69
|
+
"""Write artifacts to file in the specified format."""
|
|
70
|
+
if format == OutputFormat.JSON:
|
|
71
|
+
output_file = output_dir / "analysis.json"
|
|
72
|
+
# Use Pydantic's model_dump_json() for compact output
|
|
73
|
+
json_str = model_dump_json(artifacts, indent=None)
|
|
74
|
+
with output_file.open("w") as f:
|
|
75
|
+
f.write(json_str)
|
|
76
|
+
logger.info(f"Analysis saved to {output_file}")
|
|
77
|
+
|
|
78
|
+
elif format == OutputFormat.MSGPACK:
|
|
79
|
+
output_file = output_dir / "analysis.msgpack"
|
|
80
|
+
msgpack_data = artifacts.to_msgpack_bytes()
|
|
81
|
+
with output_file.open("wb") as f:
|
|
82
|
+
f.write(msgpack_data)
|
|
83
|
+
logger.info(f"Analysis saved to {output_file}")
|
|
84
|
+
logger.info(
|
|
85
|
+
f"Compression ratio: {artifacts.get_compression_ratio():.1%} of JSON size"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
app = typer.Typer(
|
|
89
|
+
callback=main,
|
|
90
|
+
name="codeanalyzer",
|
|
91
|
+
help="Static Analysis on Python source code using Jedi, CodeQL and Tree sitter.",
|
|
92
|
+
invoke_without_command=True,
|
|
93
|
+
no_args_is_help=True,
|
|
94
|
+
add_completion=False,
|
|
95
|
+
rich_markup_mode="rich",
|
|
96
|
+
pretty_exceptions_show_locals=False,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
app()
|
|
@@ -8,12 +8,13 @@ from typing import Any, Dict, Optional, Union, List
|
|
|
8
8
|
|
|
9
9
|
import ray
|
|
10
10
|
from codeanalyzer.utils import logger
|
|
11
|
-
from codeanalyzer.schema import PyApplication, PyModule
|
|
11
|
+
from codeanalyzer.schema import PyApplication, PyModule, model_dump_json, model_validate_json
|
|
12
12
|
from codeanalyzer.semantic_analysis.codeql import CodeQLLoader
|
|
13
13
|
from codeanalyzer.semantic_analysis.codeql.codeql_exceptions import CodeQLExceptions
|
|
14
14
|
from codeanalyzer.syntactic_analysis.exceptions import SymbolTableBuilderRayError
|
|
15
15
|
from codeanalyzer.syntactic_analysis.symbol_table_builder import SymbolTableBuilder
|
|
16
16
|
from codeanalyzer.utils import ProgressBar
|
|
17
|
+
from codeanalyzer.options import AnalysisOptions
|
|
17
18
|
|
|
18
19
|
@ray.remote
|
|
19
20
|
def _process_file_with_ray(py_file: Union[Path, str], project_dir: Union[Path, str], virtualenv: Union[Path, str, None]) -> Dict[str, PyModule]:
|
|
@@ -43,40 +44,25 @@ class Codeanalyzer:
|
|
|
43
44
|
"""Core functionality for CodeQL analysis.
|
|
44
45
|
|
|
45
46
|
Args:
|
|
46
|
-
|
|
47
|
-
virtualenv (Optional[Path]): Path to the virtual environment directory.
|
|
48
|
-
using_codeql (bool): Whether to use CodeQL for analysis.
|
|
49
|
-
rebuild_analysis (bool): Whether to force rebuild the database.
|
|
50
|
-
clear_cache (bool): Whether to delete the cached directory after analysis.
|
|
51
|
-
analysis_depth (int): Depth of analysis (reserved for future use).
|
|
47
|
+
options (AnalysisOptions): Analysis configuration options containing all necessary parameters.
|
|
52
48
|
"""
|
|
53
49
|
|
|
54
|
-
def __init__(
|
|
55
|
-
self
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
skip_tests
|
|
59
|
-
using_codeql
|
|
60
|
-
rebuild_analysis
|
|
61
|
-
cache_dir: Optional[Path],
|
|
62
|
-
clear_cache: bool,
|
|
63
|
-
using_ray: bool,
|
|
64
|
-
file_name: Optional[Path] = None,
|
|
65
|
-
) -> None:
|
|
66
|
-
self.analysis_depth = analysis_depth
|
|
67
|
-
self.project_dir = Path(project_dir).resolve()
|
|
68
|
-
self.skip_tests = skip_tests
|
|
69
|
-
self.using_codeql = using_codeql
|
|
70
|
-
self.rebuild_analysis = rebuild_analysis
|
|
50
|
+
def __init__(self, options: AnalysisOptions) -> None:
|
|
51
|
+
self.options = options
|
|
52
|
+
self.analysis_depth = options.analysis_level
|
|
53
|
+
self.project_dir = Path(options.input).resolve()
|
|
54
|
+
self.skip_tests = options.skip_tests
|
|
55
|
+
self.using_codeql = options.using_codeql
|
|
56
|
+
self.rebuild_analysis = options.rebuild_analysis
|
|
71
57
|
self.cache_dir = (
|
|
72
|
-
cache_dir.resolve() if cache_dir is not None else self.project_dir
|
|
58
|
+
options.cache_dir.resolve() if options.cache_dir is not None else self.project_dir
|
|
73
59
|
) / ".codeanalyzer"
|
|
74
|
-
self.clear_cache = clear_cache
|
|
60
|
+
self.clear_cache = options.clear_cache
|
|
75
61
|
self.db_path: Optional[Path] = None
|
|
76
62
|
self.codeql_bin: Optional[Path] = None
|
|
77
63
|
self.virtualenv: Optional[Path] = None
|
|
78
|
-
self.using_ray: bool = using_ray
|
|
79
|
-
self.file_name: Optional[Path] = file_name
|
|
64
|
+
self.using_ray: bool = options.using_ray
|
|
65
|
+
self.file_name: Optional[Path] = options.file_name
|
|
80
66
|
|
|
81
67
|
@staticmethod
|
|
82
68
|
def _cmd_exec_helper(
|
|
@@ -408,7 +394,7 @@ class Codeanalyzer:
|
|
|
408
394
|
"""
|
|
409
395
|
with cache_file.open('r') as f:
|
|
410
396
|
data = f.read()
|
|
411
|
-
return PyApplication
|
|
397
|
+
return model_validate_json(PyApplication, data)
|
|
412
398
|
|
|
413
399
|
def _save_analysis_cache(self, app: PyApplication, cache_file: Path) -> None:
|
|
414
400
|
"""Save analysis to cache file.
|
|
@@ -421,8 +407,8 @@ class Codeanalyzer:
|
|
|
421
407
|
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
422
408
|
|
|
423
409
|
with cache_file.open('w') as f:
|
|
424
|
-
f.write(app
|
|
425
|
-
|
|
410
|
+
f.write(model_dump_json(app, indent=2))
|
|
411
|
+
|
|
426
412
|
logger.info(f"Analysis cached to {cache_file}")
|
|
427
413
|
|
|
428
414
|
def _file_unchanged(self, file_path: Path, cached_module: PyModule) -> bool:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OutputFormat(str, Enum):
|
|
8
|
+
JSON = "json"
|
|
9
|
+
MSGPACK = "msgpack"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AnalysisOptions:
|
|
14
|
+
input: Path
|
|
15
|
+
output: Optional[Path] = None
|
|
16
|
+
format: OutputFormat = OutputFormat.JSON
|
|
17
|
+
analysis_level: int = 1
|
|
18
|
+
using_codeql: bool = False
|
|
19
|
+
using_ray: bool = False
|
|
20
|
+
rebuild_analysis: bool = False
|
|
21
|
+
skip_tests: bool = True
|
|
22
|
+
file_name: Optional[Path] = None
|
|
23
|
+
cache_dir: Optional[Path] = None
|
|
24
|
+
clear_cache: bool = False
|
|
25
|
+
verbosity: int = 0
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
2
|
+
from packaging.version import parse as parse_version
|
|
3
|
+
|
|
4
|
+
from .py_schema import (
|
|
5
|
+
PyApplication,
|
|
6
|
+
PyCallable,
|
|
7
|
+
PyCallableParameter,
|
|
8
|
+
PyClass,
|
|
9
|
+
PyClassAttribute,
|
|
10
|
+
PyComment,
|
|
11
|
+
PyImport,
|
|
12
|
+
PyModule,
|
|
13
|
+
PyVariableDeclaration,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"PyApplication",
|
|
18
|
+
"PyImport",
|
|
19
|
+
"PyComment",
|
|
20
|
+
"PyModule",
|
|
21
|
+
"PyClass",
|
|
22
|
+
"PyVariableDeclaration",
|
|
23
|
+
"PyCallable",
|
|
24
|
+
"PyClassAttribute",
|
|
25
|
+
"PyCallableParameter",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
pydantic_version = version("pydantic")
|
|
30
|
+
except PackageNotFoundError:
|
|
31
|
+
pydantic_version = "0.0.0" # fallback or raise if appropriate
|
|
32
|
+
|
|
33
|
+
PYDANTIC_V2 = parse_version(pydantic_version) >= parse_version("2.0.0")
|
|
34
|
+
|
|
35
|
+
if not PYDANTIC_V2:
|
|
36
|
+
# Safe to pass localns
|
|
37
|
+
PyCallable.update_forward_refs(PyClass=PyClass)
|
|
38
|
+
PyClass.update_forward_refs(PyCallable=PyCallable)
|
|
39
|
+
PyModule.update_forward_refs(PyCallable=PyCallable, PyClass=PyClass)
|
|
40
|
+
PyApplication.update_forward_refs(
|
|
41
|
+
PyCallable=PyCallable,
|
|
42
|
+
PyClass=PyClass,
|
|
43
|
+
PyModule=PyModule
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Compatibility helpers for Pydantic v1/v2
|
|
47
|
+
def model_dump_json(model, **kwargs):
|
|
48
|
+
"""Compatibility helper for JSON serialization."""
|
|
49
|
+
if PYDANTIC_V2:
|
|
50
|
+
return model.model_dump_json(**kwargs)
|
|
51
|
+
else:
|
|
52
|
+
# Map Pydantic v2 parameters to v1 equivalents
|
|
53
|
+
v1_kwargs = {}
|
|
54
|
+
if 'indent' in kwargs:
|
|
55
|
+
v1_kwargs['indent'] = kwargs['indent']
|
|
56
|
+
if 'separators' in kwargs:
|
|
57
|
+
# In v1, separators is passed to dumps_kwargs
|
|
58
|
+
v1_kwargs['separators'] = kwargs['separators']
|
|
59
|
+
return model.json(**v1_kwargs)
|
|
60
|
+
|
|
61
|
+
def model_validate_json(model_class, json_data):
|
|
62
|
+
"""Compatibility helper for JSON deserialization."""
|
|
63
|
+
if PYDANTIC_V2:
|
|
64
|
+
return model_class.model_validate_json(json_data)
|
|
65
|
+
else:
|
|
66
|
+
return model_class.parse_raw(json_data)
|
|
67
|
+
|
|
68
|
+
__all__.extend([
|
|
69
|
+
"PYDANTIC_V2",
|
|
70
|
+
"model_dump_json",
|
|
71
|
+
"model_validate_json"
|
|
72
|
+
])
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "codeanalyzer-python"
|
|
3
|
+
version = "0.1.12"
|
|
4
|
+
description = "Static Analysis on Python source code using Jedi, CodeQL and Treesitter."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Rahul Krishna", email = "i.m.ralk@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
# jedi
|
|
13
|
+
"jedi>=0.18.0,<0.20.0; python_version < '3.11'",
|
|
14
|
+
"jedi<=0.19.2; python_version >= '3.11'",
|
|
15
|
+
# msgpack
|
|
16
|
+
"msgpack>=1.0.0,<1.0.7; python_version < '3.11'",
|
|
17
|
+
"msgpack>=1.0.7,<2.0.0; python_version >= '3.11'",
|
|
18
|
+
# networkx
|
|
19
|
+
"networkx>=2.6.0,<3.2.0; python_version < '3.11'",
|
|
20
|
+
"networkx>=3.0.0,<4.0.0; python_version >= '3.11'",
|
|
21
|
+
# pandas
|
|
22
|
+
"pandas>=1.3.0,<2.0.0; python_version < '3.11'",
|
|
23
|
+
"pandas>=2.0.0,<3.0.0; python_version >= '3.11'",
|
|
24
|
+
# numpy
|
|
25
|
+
"numpy>=1.21.0,<1.24.0; python_version < '3.11'",
|
|
26
|
+
"numpy>=1.24.0,<2.0.0; python_version >= '3.11' and python_version < '3.12'",
|
|
27
|
+
"numpy>=1.26.0,<2.0.0; python_version >= '3.12'",
|
|
28
|
+
# pydantic
|
|
29
|
+
"pydantic>=1.8.0,<2.0.0; python_version < '3.11'",
|
|
30
|
+
"pydantic>=2.0.0,<3.0.0; python_version >= '3.11'",
|
|
31
|
+
# requests
|
|
32
|
+
"requests>=2.20.0,<3.0.0; python_version >= '3.11'",
|
|
33
|
+
# rich
|
|
34
|
+
"rich>=12.6.0,<14.0.0; python_version < '3.11'",
|
|
35
|
+
"rich>=14.0.0,<15.0.0; python_version >= '3.11'",
|
|
36
|
+
# typer
|
|
37
|
+
"typer>=0.9.0,<1.0.0; python_version < '3.11'",
|
|
38
|
+
"typer>=0.9.0,<2.0.0; python_version >= '3.11'",
|
|
39
|
+
# typing-extensions
|
|
40
|
+
"typing-extensions>=4.0.0,<5.0.0; python_version < '3.11'",
|
|
41
|
+
"typing-extensions>=4.5.0,<6.0.0; python_version >= '3.11'",
|
|
42
|
+
# ray
|
|
43
|
+
"ray==2.0.0; python_version < '3.11'",
|
|
44
|
+
"ray>=2.10.0,<3.0.0; python_version >= '3.11'",
|
|
45
|
+
"packaging>=25.0",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
test = [
|
|
50
|
+
"pytest>=7.0.0,<8.0.0",
|
|
51
|
+
"pytest-asyncio>=0.14.0,<0.15.0",
|
|
52
|
+
"pytest-cov>=2.10.0,<3.0.0",
|
|
53
|
+
"pytest-pspec>=0.0.3"
|
|
54
|
+
]
|
|
55
|
+
dev = [
|
|
56
|
+
"ipdb>=0.13.0,<0.14.0",
|
|
57
|
+
"pre-commit>=2.9.0,<3.0.0"
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[project.scripts]
|
|
61
|
+
codeanalyzer = "codeanalyzer.__main__:app"
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["hatchling"]
|
|
65
|
+
build-backend = "hatchling.build"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["codeanalyzer"]
|
|
69
|
+
include = ["codeanalyzer/py.typed"]
|
|
70
|
+
|
|
71
|
+
[tool.hatch.build.targets.sdist]
|
|
72
|
+
include = [
|
|
73
|
+
"codeanalyzer",
|
|
74
|
+
"codeanalyzer/py.typed",
|
|
75
|
+
"README.md",
|
|
76
|
+
"LICENSE",
|
|
77
|
+
"NOTICE"
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
addopts = [
|
|
82
|
+
"-p", "coverage",
|
|
83
|
+
"--cov=codeanalyzer",
|
|
84
|
+
"--cov-report=html",
|
|
85
|
+
"--cov-report=term-missing",
|
|
86
|
+
"--cov-fail-under=40",
|
|
87
|
+
"--ignore=test/fixtures"
|
|
88
|
+
]
|
|
89
|
+
testpaths = ["test"]
|
|
90
|
+
|
|
91
|
+
[tool.coverage.run]
|
|
92
|
+
source = ["codeanalyzer"]
|
|
93
|
+
branch = true
|
|
94
|
+
omit = [
|
|
95
|
+
"*/tests/*",
|
|
96
|
+
"*/test_*",
|
|
97
|
+
"*/__pycache__/*",
|
|
98
|
+
"*/venv/*",
|
|
99
|
+
"*/.venv/*",
|
|
100
|
+
"codeanalyzer/semantic_analysis/*"
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.coverage.report]
|
|
104
|
+
precision = 2
|
|
105
|
+
show_missing = true
|
|
106
|
+
exclude_lines = [
|
|
107
|
+
"pragma: no cover",
|
|
108
|
+
"def __repr__",
|
|
109
|
+
"raise AssertionError",
|
|
110
|
+
"raise NotImplementedError",
|
|
111
|
+
"if __name__ == .__main__.:"
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
[tool.coverage.html]
|
|
115
|
+
directory = "htmlcov"
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Optional, Annotated
|
|
3
|
-
|
|
4
|
-
import typer
|
|
5
|
-
|
|
6
|
-
from codeanalyzer.core import Codeanalyzer
|
|
7
|
-
from codeanalyzer.utils import _set_log_level, logger
|
|
8
|
-
from codeanalyzer.config import OutputFormat
|
|
9
|
-
|
|
10
|
-
def main(
|
|
11
|
-
input: Annotated[
|
|
12
|
-
Path, typer.Option("-i", "--input", help="Path to the project root directory.")
|
|
13
|
-
],
|
|
14
|
-
output: Annotated[
|
|
15
|
-
Optional[Path],
|
|
16
|
-
typer.Option("-o", "--output", help="Output directory for artifacts."),
|
|
17
|
-
] = None,
|
|
18
|
-
format: Annotated[
|
|
19
|
-
OutputFormat,
|
|
20
|
-
typer.Option(
|
|
21
|
-
"-f",
|
|
22
|
-
"--format",
|
|
23
|
-
help="Output format: json or msgpack.",
|
|
24
|
-
case_sensitive=False,
|
|
25
|
-
),
|
|
26
|
-
] = OutputFormat.JSON,
|
|
27
|
-
analysis_level: Annotated[
|
|
28
|
-
int,
|
|
29
|
-
typer.Option("-a", "--analysis-level", help="1: symbol table, 2: call graph."),
|
|
30
|
-
] = 1,
|
|
31
|
-
using_codeql: Annotated[
|
|
32
|
-
bool, typer.Option("--codeql/--no-codeql", help="Enable CodeQL-based analysis.")
|
|
33
|
-
] = False,
|
|
34
|
-
using_ray: Annotated[
|
|
35
|
-
bool,
|
|
36
|
-
typer.Option(
|
|
37
|
-
"--ray/--no-ray", help="Enable Ray for distributed analysis."
|
|
38
|
-
),
|
|
39
|
-
] = False,
|
|
40
|
-
rebuild_analysis: Annotated[
|
|
41
|
-
bool,
|
|
42
|
-
typer.Option(
|
|
43
|
-
"--eager/--lazy",
|
|
44
|
-
help="Enable eager or lazy analysis. Defaults to lazy.",
|
|
45
|
-
),
|
|
46
|
-
] = False,
|
|
47
|
-
skip_tests: Annotated[
|
|
48
|
-
bool,
|
|
49
|
-
typer.Option(
|
|
50
|
-
"--skip-tests/--include-tests",
|
|
51
|
-
help="Skip test files in analysis.",
|
|
52
|
-
),
|
|
53
|
-
] = True,
|
|
54
|
-
file_name: Annotated[
|
|
55
|
-
Optional[Path],
|
|
56
|
-
typer.Option(
|
|
57
|
-
"--file-name",
|
|
58
|
-
help="Analyze only the specified file (relative to input directory).",
|
|
59
|
-
),
|
|
60
|
-
] = None,
|
|
61
|
-
cache_dir: Annotated[
|
|
62
|
-
Optional[Path],
|
|
63
|
-
typer.Option(
|
|
64
|
-
"-c",
|
|
65
|
-
"--cache-dir",
|
|
66
|
-
help="Directory to store analysis cache. Defaults to '.codeanalyzer' in the input directory.",
|
|
67
|
-
),
|
|
68
|
-
] = None,
|
|
69
|
-
clear_cache: Annotated[
|
|
70
|
-
bool,
|
|
71
|
-
typer.Option("--clear-cache/--keep-cache", help="Clear cache after analysis. By default, cache is retained."),
|
|
72
|
-
] = False,
|
|
73
|
-
verbosity: Annotated[
|
|
74
|
-
int, typer.Option("-v", count=True, help="Increase verbosity: -v, -vv, -vvv")
|
|
75
|
-
] = 0,
|
|
76
|
-
):
|
|
77
|
-
"""Static Analysis on Python source code using Jedi, Astroid, and Treesitter."""
|
|
78
|
-
_set_log_level(verbosity)
|
|
79
|
-
|
|
80
|
-
if not input.exists():
|
|
81
|
-
logger.error(f"Input path '{input}' does not exist.")
|
|
82
|
-
raise typer.Exit(code=1)
|
|
83
|
-
|
|
84
|
-
# Validate file_name if provided
|
|
85
|
-
if file_name is not None:
|
|
86
|
-
full_file_path = input / file_name
|
|
87
|
-
if not full_file_path.exists():
|
|
88
|
-
logger.error(f"Specified file '{file_name}' does not exist in '{input}'.")
|
|
89
|
-
raise typer.Exit(code=1)
|
|
90
|
-
if not full_file_path.is_file():
|
|
91
|
-
logger.error(f"Specified path '{file_name}' is not a file.")
|
|
92
|
-
raise typer.Exit(code=1)
|
|
93
|
-
if not str(file_name).endswith('.py'):
|
|
94
|
-
logger.error(f"Specified file '{file_name}' is not a Python file (.py).")
|
|
95
|
-
raise typer.Exit(code=1)
|
|
96
|
-
|
|
97
|
-
with Codeanalyzer(
|
|
98
|
-
input, analysis_level, skip_tests, using_codeql, rebuild_analysis, cache_dir, clear_cache, using_ray, file_name
|
|
99
|
-
) as analyzer:
|
|
100
|
-
artifacts = analyzer.analyze()
|
|
101
|
-
|
|
102
|
-
# Handle output based on format
|
|
103
|
-
if output is None:
|
|
104
|
-
# Output to stdout (only for JSON)
|
|
105
|
-
print(artifacts.json(separators=(",", ":")))
|
|
106
|
-
else:
|
|
107
|
-
# Output to file
|
|
108
|
-
output.mkdir(parents=True, exist_ok=True)
|
|
109
|
-
_write_output(artifacts, output, format)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _write_output(artifacts, output_dir: Path, format: OutputFormat):
|
|
113
|
-
"""Write artifacts to file in the specified format."""
|
|
114
|
-
if format == OutputFormat.JSON:
|
|
115
|
-
output_file = output_dir / "analysis.json"
|
|
116
|
-
# Use Pydantic's json() with separators for compact output
|
|
117
|
-
json_str = artifacts.json(indent=None)
|
|
118
|
-
with output_file.open("w") as f:
|
|
119
|
-
f.write(json_str)
|
|
120
|
-
logger.info(f"Analysis saved to {output_file}")
|
|
121
|
-
|
|
122
|
-
elif format == OutputFormat.MSGPACK:
|
|
123
|
-
output_file = output_dir / "analysis.msgpack"
|
|
124
|
-
msgpack_data = artifacts.to_msgpack_bytes()
|
|
125
|
-
with output_file.open("wb") as f:
|
|
126
|
-
f.write(msgpack_data)
|
|
127
|
-
logger.info(f"Analysis saved to {output_file}")
|
|
128
|
-
logger.info(
|
|
129
|
-
f"Compression ratio: {artifacts.get_compression_ratio():.1%} of JSON size"
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
app = typer.Typer(
|
|
134
|
-
callback=main,
|
|
135
|
-
name="codeanalyzer",
|
|
136
|
-
help="Static Analysis on Python source code using Jedi, CodeQL and Tree sitter.",
|
|
137
|
-
invoke_without_command=True,
|
|
138
|
-
no_args_is_help=True,
|
|
139
|
-
add_completion=False,
|
|
140
|
-
rich_markup_mode="rich",
|
|
141
|
-
pretty_exceptions_show_locals=False,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
if __name__ == "__main__":
|
|
145
|
-
app()
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from .py_schema import (
|
|
2
|
-
PyApplication,
|
|
3
|
-
PyCallable,
|
|
4
|
-
PyCallableParameter,
|
|
5
|
-
PyClass,
|
|
6
|
-
PyClassAttribute,
|
|
7
|
-
PyComment,
|
|
8
|
-
PyImport,
|
|
9
|
-
PyModule,
|
|
10
|
-
PyVariableDeclaration,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"PyApplication",
|
|
15
|
-
"PyImport",
|
|
16
|
-
"PyComment",
|
|
17
|
-
"PyModule",
|
|
18
|
-
"PyClass",
|
|
19
|
-
"PyVariableDeclaration",
|
|
20
|
-
"PyCallable",
|
|
21
|
-
"PyClassAttribute",
|
|
22
|
-
"PyCallableParameter",
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
# Resolve forward references
|
|
26
|
-
PyCallable.update_forward_refs(PyClass=PyClass)
|
|
27
|
-
PyClass.update_forward_refs(PyCallable=PyCallable)
|
|
28
|
-
PyModule.update_forward_refs(PyCallable=PyCallable, PyClass=PyClass)
|
|
29
|
-
PyApplication.update_forward_refs(
|
|
30
|
-
PyCallable=PyCallable,
|
|
31
|
-
PyClass=PyClass,
|
|
32
|
-
PyModule=PyModule
|
|
33
|
-
)
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "codeanalyzer-python"
|
|
3
|
-
version = "0.1.10"
|
|
4
|
-
description = "Static Analysis on Python source code using Jedi, CodeQL and Treesitter."
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
authors = [
|
|
7
|
-
{ name = "Rahul Krishna", email = "i.m.ralk@gmail.com" }
|
|
8
|
-
]
|
|
9
|
-
requires-python = ">=3.9"
|
|
10
|
-
|
|
11
|
-
dependencies = [
|
|
12
|
-
"jedi>=0.18.0,<0.20.0",
|
|
13
|
-
"msgpack>=1.0.0,<1.0.7",
|
|
14
|
-
"networkx>=2.6.0,<3.2.0",
|
|
15
|
-
"pandas>=1.3.0,<2.0.0",
|
|
16
|
-
"numpy>=1.21.0,<1.24.0",
|
|
17
|
-
"pydantic>=1.8.0,<2.0.0",
|
|
18
|
-
"requests>=2.20.0,<3.0.0",
|
|
19
|
-
"rich>=12.6.0,<14.0.0",
|
|
20
|
-
"typer>=0.9.0,<1.0.0",
|
|
21
|
-
"ray>=2.0.0,<3.0.0",
|
|
22
|
-
"typing-extensions>=4.0.0"
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
[dependency-groups]
|
|
26
|
-
test = [
|
|
27
|
-
"pytest>=7.0.0,<8.0.0",
|
|
28
|
-
"pytest-asyncio>=0.14.0,<0.15.0",
|
|
29
|
-
"pytest-cov>=2.10.0,<3.0.0",
|
|
30
|
-
"pytest-pspec>=0.0.3"
|
|
31
|
-
]
|
|
32
|
-
dev = [
|
|
33
|
-
"ipdb>=0.13.0,<0.14.0",
|
|
34
|
-
"pre-commit>=2.9.0,<3.0.0"
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
[project.scripts]
|
|
38
|
-
codeanalyzer = "codeanalyzer.__main__:app"
|
|
39
|
-
|
|
40
|
-
[build-system]
|
|
41
|
-
requires = ["hatchling"]
|
|
42
|
-
build-backend = "hatchling.build"
|
|
43
|
-
|
|
44
|
-
[tool.hatch.build.targets.wheel]
|
|
45
|
-
packages = ["codeanalyzer"]
|
|
46
|
-
include = ["codeanalyzer/py.typed"]
|
|
47
|
-
|
|
48
|
-
[tool.hatch.build.targets.sdist]
|
|
49
|
-
include = [
|
|
50
|
-
"codeanalyzer",
|
|
51
|
-
"codeanalyzer/py.typed",
|
|
52
|
-
"README.md",
|
|
53
|
-
"LICENSE",
|
|
54
|
-
"NOTICE"
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
[tool.pytest.ini_options]
|
|
58
|
-
addopts = [
|
|
59
|
-
"-p", "coverage",
|
|
60
|
-
"--cov=codeanalyzer",
|
|
61
|
-
"--cov-report=html",
|
|
62
|
-
"--cov-report=term-missing",
|
|
63
|
-
"--cov-fail-under=40",
|
|
64
|
-
"--ignore=test/fixtures"
|
|
65
|
-
]
|
|
66
|
-
testpaths = ["test"]
|
|
67
|
-
|
|
68
|
-
[tool.coverage.run]
|
|
69
|
-
source = ["codeanalyzer"]
|
|
70
|
-
branch = true
|
|
71
|
-
omit = [
|
|
72
|
-
"*/tests/*",
|
|
73
|
-
"*/test_*",
|
|
74
|
-
"*/__pycache__/*",
|
|
75
|
-
"*/venv/*",
|
|
76
|
-
"*/.venv/*",
|
|
77
|
-
"codeanalyzer/semantic_analysis/*"
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
[tool.coverage.report]
|
|
81
|
-
precision = 2
|
|
82
|
-
show_missing = true
|
|
83
|
-
exclude_lines = [
|
|
84
|
-
"pragma: no cover",
|
|
85
|
-
"def __repr__",
|
|
86
|
-
"raise AssertionError",
|
|
87
|
-
"raise NotImplementedError",
|
|
88
|
-
"if __name__ == .__main__.:"
|
|
89
|
-
]
|
|
90
|
-
|
|
91
|
-
[tool.coverage.html]
|
|
92
|
-
directory = "htmlcov"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/progress_bar.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|