Skip to content

Commit efd63a9

Browse files
committed
refactor: replace Codeanalyzer constructor parameters with AnalysisOptions dataclass
BREAKING CHANGE: Codeanalyzer constructor now takes a single AnalysisOptions parameter instead of 9 individual parameters, improving API design and maintainability. - Add new codeanalyzer.options module with AnalysisOptions dataclass - Refactor Codeanalyzer.__init__ to accept AnalysisOptions parameter - Update CLI to create AnalysisOptions instance from command arguments - Improve type safety and configuration management - Enhance code organization and maintainability - Update version to 0.1.12 in pyproject.toml - Add comprehensive changelog documentation - Remove obsolete test_numpy_constraints.py file Closes #12
1 parent 82882df commit efd63a9

File tree

7 files changed

+107
-155
lines changed

7 files changed

+107
-155
lines changed

CHANGELOG.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,38 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.12] - 2025-07-21
9+
10+
### Changed
11+
- **BREAKING CHANGE**: Refactored `Codeanalyzer` constructor to use `AnalysisOptions` dataclass [in response to #19](https://github.com/codellm-devkit/codeanalyzer-python/issues/19)
12+
- Replaced multiple individual parameters with single `AnalysisOptions` object for cleaner API
13+
- Improved type safety and configuration management through centralized options structure
14+
- Enhanced maintainability and extensibility for future configuration additions
15+
- Updated CLI integration to create and pass `AnalysisOptions` instance
16+
- Maintained backward compatibility in terms of functionality while improving code architecture
17+
18+
### Added
19+
- New `AnalysisOptions` dataclass in `codeanalyzer.options` module [in response to #19](https://github.com/codellm-devkit/codeanalyzer-python/issues/19)
20+
- Centralized configuration structure with all analysis parameters
21+
- Type-safe configuration with proper defaults and validation
22+
- Support for `OutputFormat` enum integration
23+
- Clean separation between CLI and library configuration handling
24+
25+
### Technical Details
26+
- Added new `codeanalyzer.options` package with `AnalysisOptions` dataclass
27+
- Updated `Codeanalyzer.__init__()` to accept single `options` parameter instead of 9 individual parameters
28+
- Modified CLI handler in `__main__.py` to create `AnalysisOptions` instance from command line arguments
29+
- Improved code organization and maintainability for configuration management
30+
- Enhanced API design following best practices for parameter object patterns
31+
832
## [0.1.11] - 2025-07-21
933

1034
### Fixed
1135
- **CRITICAL**: Fixed NumPy build failure on Python 3.12+ (addresses [#19](https://github.com/codellm-devkit/codeanalyzer-python/issues/19))
1236
- Updated NumPy dependency constraints to handle Python 3.12+ compatibility
1337
- Split NumPy version constraints into three tiers:
1438
- `numpy>=1.21.0,<1.24.0` for Python < 3.11
15-
- `numpy>=1.24.0,<2.0.0` for Python 3.11.x
39+
- `numpy>=1.24.0,<2.0.0` for Python 3.11.x
1640
- `numpy>=1.26.0,<2.0.0` for Python 3.12+ (requires NumPy 1.26+ which supports Python 3.12)
1741
- Resolves `ModuleNotFoundError: No module named 'distutils'` errors on Python 3.12+
1842
- Ensures compatibility with Python 3.12 which removed `distutils` from the standard library

codeanalyzer/__main__.py

Lines changed: 40 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -7,107 +7,62 @@
77
from codeanalyzer.utils import _set_log_level, logger
88
from codeanalyzer.config import OutputFormat
99
from codeanalyzer.schema import model_dump_json
10+
from codeanalyzer.options import AnalysisOptions
1011

1112
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,
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),
7725
):
78-
"""Static Analysis on Python source code using Jedi, Astroid, and Treesitter."""
79-
_set_log_level(verbosity)
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+
)
8040

81-
if not input.exists():
82-
logger.error(f"Input path '{input}' does not exist.")
41+
_set_log_level(options.verbosity)
42+
if not options.input.exists():
43+
logger.error(f"Input path '{options.input}' does not exist.")
8344
raise typer.Exit(code=1)
8445

85-
# Validate file_name if provided
86-
if file_name is not None:
87-
full_file_path = input / file_name
46+
if options.file_name is not None:
47+
full_file_path = options.input / options.file_name
8848
if not full_file_path.exists():
89-
logger.error(f"Specified file '{file_name}' does not exist in '{input}'.")
49+
logger.error(f"Specified file '{options.file_name}' does not exist in '{options.input}'.")
9050
raise typer.Exit(code=1)
9151
if not full_file_path.is_file():
92-
logger.error(f"Specified path '{file_name}' is not a file.")
52+
logger.error(f"Specified path '{options.file_name}' is not a file.")
9353
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).")
54+
if not str(options.file_name).endswith('.py'):
55+
logger.error(f"Specified file '{options.file_name}' is not a Python file (.py).")
9656
raise typer.Exit(code=1)
9757

98-
with Codeanalyzer(
99-
input, analysis_level, skip_tests, using_codeql, rebuild_analysis, cache_dir, clear_cache, using_ray, file_name
100-
) as analyzer:
58+
with Codeanalyzer(options) as analyzer:
10159
artifacts = analyzer.analyze()
10260

103-
# Handle output based on format
104-
if output is None:
105-
# Output to stdout (only for JSON)
61+
if options.output is None:
10662
print(model_dump_json(artifacts, separators=(",", ":")))
10763
else:
108-
# Output to file
109-
output.mkdir(parents=True, exist_ok=True)
110-
_write_output(artifacts, output, format)
64+
options.output.mkdir(parents=True, exist_ok=True)
65+
_write_output(artifacts, options.output, options.format)
11166

11267

11368
def _write_output(artifacts, output_dir: Path, format: OutputFormat):
@@ -130,7 +85,6 @@ def _write_output(artifacts, output_dir: Path, format: OutputFormat):
13085
f"Compression ratio: {artifacts.get_compression_ratio():.1%} of JSON size"
13186
)
13287

133-
13488
app = typer.Typer(
13589
callback=main,
13690
name="codeanalyzer",

codeanalyzer/core.py

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from codeanalyzer.syntactic_analysis.exceptions import SymbolTableBuilderRayError
1515
from codeanalyzer.syntactic_analysis.symbol_table_builder import SymbolTableBuilder
1616
from codeanalyzer.utils import ProgressBar
17+
from codeanalyzer.options import AnalysisOptions
1718

1819
@ray.remote
1920
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:
4344
"""Core functionality for CodeQL analysis.
4445
4546
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.
5248
"""
5349

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
7157
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
7359
) / ".codeanalyzer"
74-
self.clear_cache = clear_cache
60+
self.clear_cache = options.clear_cache
7561
self.db_path: Optional[Path] = None
7662
self.codeql_bin: Optional[Path] = None
7763
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
8066

8167
@staticmethod
8268
def _cmd_exec_helper(

codeanalyzer/options/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .options import AnalysisOptions
2+
3+
__all__ = ["AnalysisOptions"]

codeanalyzer/options/options.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "codeanalyzer-python"
3-
version = "0.1.11"
3+
version = "0.1.12"
44
description = "Static Analysis on Python source code using Jedi, CodeQL and Treesitter."
55
readme = "README.md"
66
authors = [

test_numpy_constraints.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)