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.
Files changed (40) hide show
  1. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/PKG-INFO +24 -12
  2. codeanalyzer_python-0.1.12/codeanalyzer/__main__.py +100 -0
  3. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/core.py +17 -31
  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.12/codeanalyzer/schema/__init__.py +72 -0
  7. codeanalyzer_python-0.1.12/pyproject.toml +115 -0
  8. codeanalyzer_python-0.1.10/codeanalyzer/__main__.py +0 -145
  9. codeanalyzer_python-0.1.10/codeanalyzer/schema/__init__.py +0 -33
  10. codeanalyzer_python-0.1.10/pyproject.toml +0 -92
  11. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/.gitignore +0 -0
  12. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/LICENSE +0 -0
  13. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/NOTICE +0 -0
  14. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/README.md +0 -0
  15. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/__init__.py +0 -0
  16. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/config/__init__.py +0 -0
  17. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/config/config.py +0 -0
  18. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/__init__.py +0 -0
  19. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/jedi/jedi.py +0 -0
  20. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/py.typed +0 -0
  21. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/schema/py_schema.py +0 -0
  22. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/__init__.py +0 -0
  23. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/__init__.py +0 -0
  24. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_analysis.py +0 -0
  25. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_exceptions.py +0 -0
  26. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_loader.py +0 -0
  27. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/codeql/codeql_query_runner.py +0 -0
  28. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/semantic_analysis/wala/__init__.py +0 -0
  29. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/__init__.py +0 -0
  30. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/exceptions.py +0 -0
  31. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/syntactic_analysis/symbol_table_builder.py +0 -0
  32. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/__init__.py +0 -0
  33. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/logging.py +0 -0
  34. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/codeanalyzer/utils/progress_bar.py +0 -0
  35. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/LICENSE +0 -0
  36. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/README.md +0 -0
  37. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/properties/README.md +0 -0
  38. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/LICENSE +0 -0
  39. {codeanalyzer_python-0.1.10 → codeanalyzer_python-0.1.12}/test/fixtures/whole_applications/xarray/xarray/datatree_/README.md +0 -0
  40. {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.10
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: msgpack<1.0.7,>=1.0.0
11
- Requires-Dist: networkx<3.2.0,>=2.6.0
12
- Requires-Dist: numpy<1.24.0,>=1.21.0
13
- Requires-Dist: pandas<2.0.0,>=1.3.0
14
- Requires-Dist: pydantic<2.0.0,>=1.8.0
15
- Requires-Dist: ray<3.0.0,>=2.0.0
16
- Requires-Dist: requests<3.0.0,>=2.20.0
17
- Requires-Dist: rich<14.0.0,>=12.6.0
18
- Requires-Dist: typer<1.0.0,>=0.9.0
19
- Requires-Dist: typing-extensions>=4.0.0
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
  ![logo](https://github.com/codellm-devkit/codeanalyzer-python/blob/main/docs/assets/logo.png?raw=true)
@@ -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
- 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(
@@ -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.parse_raw(data)
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.json(indent=2))
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,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
@@ -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"