codeanalyzer-python 0.1.11__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.
Files changed (38) hide show
  1. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/PKG-INFO +1 -1
  2. codeanalyzer_python-0.1.12/codeanalyzer/__main__.py +100 -0
  3. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/core.py +13 -27
  4. codeanalyzer_python-0.1.12/codeanalyzer/options/__init__.py +3 -0
  5. codeanalyzer_python-0.1.12/codeanalyzer/options/options.py +25 -0
  6. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/pyproject.toml +1 -1
  7. codeanalyzer_python-0.1.11/codeanalyzer/__main__.py +0 -146
  8. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/.gitignore +0 -0
  9. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/LICENSE +0 -0
  10. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/NOTICE +0 -0
  11. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/README.md +0 -0
  12. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/__init__.py +0 -0
  13. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/config/__init__.py +0 -0
  14. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/config/config.py +0 -0
  15. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/__init__.py +0 -0
  16. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/jedi.py +0 -0
  17. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/py.typed +0 -0
  18. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/schema/__init__.py +0 -0
  19. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/schema/py_schema.py +0 -0
  20. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/__init__.py +0 -0
  21. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/__init__.py +0 -0
  22. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_analysis.py +0 -0
  23. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_exceptions.py +0 -0
  24. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_loader.py +0 -0
  25. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_query_runner.py +0 -0
  26. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/wala/__init__.py +0 -0
  27. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/__init__.py +0 -0
  28. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/exceptions.py +0 -0
  29. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/symbol_table_builder.py +0 -0
  30. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/__init__.py +0 -0
  31. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/logging.py +0 -0
  32. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/progress_bar.py +0 -0
  33. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/LICENSE +0 -0
  34. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/README.md +0 -0
  35. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/properties/README.md +0 -0
  36. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/LICENSE +0 -0
  37. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/README.md +0 -0
  38. {codeanalyzer_python-0.1.11 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/docs/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeanalyzer-python
3
- Version: 0.1.11
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
@@ -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()
@@ -14,6 +14,7 @@ from codeanalyzer.semantic_analysis.codeql.codeql_exceptions import CodeQLExcept
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
- project_dir (Union[str, Path]): The root directory of the project to analyze.
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
- project_dir: Union[str, Path],
57
- analysis_depth: int,
58
- skip_tests: bool,
59
- using_codeql: bool,
60
- rebuild_analysis: bool,
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(
@@ -0,0 +1,3 @@
1
+ from .options import AnalysisOptions
2
+
3
+ __all__ = ["AnalysisOptions"]
@@ -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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codeanalyzer-python"
3
- version = "0.1.11"
3
+ version = "0.1.12"
4
4
  description = "Static Analysis on Python source code using Jedi, CodeQL and Treesitter."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,146 +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
- from codeanalyzer.schema import model_dump_json
10
-
11
- def main(
12
- input: Annotated[
13
- Path, typer.Option("-i", "--input", help="Path to the project root directory.")
14
- ],
15
- output: Annotated[
16
- Optional[Path],
17
- typer.Option("-o", "--output", help="Output directory for artifacts."),
18
- ] = None,
19
- format: Annotated[
20
- OutputFormat,
21
- typer.Option(
22
- "-f",
23
- "--format",
24
- help="Output format: json or msgpack.",
25
- case_sensitive=False,
26
- ),
27
- ] = OutputFormat.JSON,
28
- analysis_level: Annotated[
29
- int,
30
- typer.Option("-a", "--analysis-level", help="1: symbol table, 2: call graph."),
31
- ] = 1,
32
- using_codeql: Annotated[
33
- bool, typer.Option("--codeql/--no-codeql", help="Enable CodeQL-based analysis.")
34
- ] = False,
35
- using_ray: Annotated[
36
- bool,
37
- typer.Option(
38
- "--ray/--no-ray", help="Enable Ray for distributed analysis."
39
- ),
40
- ] = False,
41
- rebuild_analysis: Annotated[
42
- bool,
43
- typer.Option(
44
- "--eager/--lazy",
45
- help="Enable eager or lazy analysis. Defaults to lazy.",
46
- ),
47
- ] = False,
48
- skip_tests: Annotated[
49
- bool,
50
- typer.Option(
51
- "--skip-tests/--include-tests",
52
- help="Skip test files in analysis.",
53
- ),
54
- ] = True,
55
- file_name: Annotated[
56
- Optional[Path],
57
- typer.Option(
58
- "--file-name",
59
- help="Analyze only the specified file (relative to input directory).",
60
- ),
61
- ] = None,
62
- cache_dir: Annotated[
63
- Optional[Path],
64
- typer.Option(
65
- "-c",
66
- "--cache-dir",
67
- help="Directory to store analysis cache. Defaults to '.codeanalyzer' in the input directory.",
68
- ),
69
- ] = None,
70
- clear_cache: Annotated[
71
- bool,
72
- typer.Option("--clear-cache/--keep-cache", help="Clear cache after analysis. By default, cache is retained."),
73
- ] = False,
74
- verbosity: Annotated[
75
- int, typer.Option("-v", count=True, help="Increase verbosity: -v, -vv, -vvv")
76
- ] = 0,
77
- ):
78
- """Static Analysis on Python source code using Jedi, Astroid, and Treesitter."""
79
- _set_log_level(verbosity)
80
-
81
- if not input.exists():
82
- logger.error(f"Input path '{input}' does not exist.")
83
- raise typer.Exit(code=1)
84
-
85
- # Validate file_name if provided
86
- if file_name is not None:
87
- full_file_path = input / file_name
88
- if not full_file_path.exists():
89
- logger.error(f"Specified file '{file_name}' does not exist in '{input}'.")
90
- raise typer.Exit(code=1)
91
- if not full_file_path.is_file():
92
- logger.error(f"Specified path '{file_name}' is not a file.")
93
- raise typer.Exit(code=1)
94
- if not str(file_name).endswith('.py'):
95
- logger.error(f"Specified file '{file_name}' is not a Python file (.py).")
96
- raise typer.Exit(code=1)
97
-
98
- with Codeanalyzer(
99
- input, analysis_level, skip_tests, using_codeql, rebuild_analysis, cache_dir, clear_cache, using_ray, file_name
100
- ) as analyzer:
101
- artifacts = analyzer.analyze()
102
-
103
- # Handle output based on format
104
- if output is None:
105
- # Output to stdout (only for JSON)
106
- print(model_dump_json(artifacts, separators=(",", ":")))
107
- else:
108
- # Output to file
109
- output.mkdir(parents=True, exist_ok=True)
110
- _write_output(artifacts, output, format)
111
-
112
-
113
- def _write_output(artifacts, output_dir: Path, format: OutputFormat):
114
- """Write artifacts to file in the specified format."""
115
- if format == OutputFormat.JSON:
116
- output_file = output_dir / "analysis.json"
117
- # Use Pydantic's model_dump_json() for compact output
118
- json_str = model_dump_json(artifacts, indent=None)
119
- with output_file.open("w") as f:
120
- f.write(json_str)
121
- logger.info(f"Analysis saved to {output_file}")
122
-
123
- elif format == OutputFormat.MSGPACK:
124
- output_file = output_dir / "analysis.msgpack"
125
- msgpack_data = artifacts.to_msgpack_bytes()
126
- with output_file.open("wb") as f:
127
- f.write(msgpack_data)
128
- logger.info(f"Analysis saved to {output_file}")
129
- logger.info(
130
- f"Compression ratio: {artifacts.get_compression_ratio():.1%} of JSON size"
131
- )
132
-
133
-
134
- app = typer.Typer(
135
- callback=main,
136
- name="codeanalyzer",
137
- help="Static Analysis on Python source code using Jedi, CodeQL and Tree sitter.",
138
- invoke_without_command=True,
139
- no_args_is_help=True,
140
- add_completion=False,
141
- rich_markup_mode="rich",
142
- pretty_exceptions_show_locals=False,
143
- )
144
-
145
- if __name__ == "__main__":
146
- app()