python-code-validator 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,47 @@
1
1
  """A flexible framework for static validation of Python code.
2
2
 
3
- This package provides a comprehensive toolkit for statically analyzing Python source
4
- code based on a declarative set of rules defined in a JSON format. It allows
5
- for checking syntax, style, structure, and constraints without executing the code.
6
-
7
- Key components exposed by this package include:
8
- - StaticValidator: The main orchestrator for running the validation process.
9
- - AppConfig: A dataclass for configuring the validator's behavior.
10
- - ExitCode: An Enum defining exit codes for CLI operations.
11
- - Custom Exceptions: For fine-grained error handling during validation.
3
+ This package provides a comprehensive toolkit for statically analyzing Python
4
+ source code based on a declarative set of rules defined in a JSON format. It
5
+ allows for checking syntax, style, structure, and constraints without
6
+ executing the code.
7
+
8
+ The primary entry point for using this package programmatically is the
9
+ `StaticValidator` class.
10
+
11
+ Example:
12
+ A minimal example of using the validator as a library.
13
+
14
+ .. code-block:: python
15
+
16
+ from code_validator import StaticValidator, AppConfig, LogLevel
17
+ from code_validator.output import Console, setup_logging
18
+ from pathlib import Path
19
+
20
+ # Basic setup
21
+ logger = setup_logging(LogLevel.INFO)
22
+ console = Console(logger)
23
+ config = AppConfig(
24
+ solution_path=Path("path/to/solution.py"),
25
+ rules_path=Path("path/to/rules.json"),
26
+ log_level=LogLevel.INFO,
27
+ is_silent=False,
28
+ stop_on_first_fail=False
29
+ )
30
+
31
+ # Run validation
32
+ validator = StaticValidator(config, console)
33
+ is_valid = validator.run()
34
+
35
+ if is_valid:
36
+ print("Validation Passed!")
37
+
38
+ Attributes:
39
+ __version__ (str): The current version of the package.
40
+ __all__ (list[str]): The list of public objects exposed by the package.
41
+
12
42
  """
13
43
 
14
- from .config import AppConfig, ExitCode
44
+ from .config import AppConfig, ExitCode, LogLevel
15
45
  from .core import StaticValidator
16
46
  from .exceptions import RuleParsingError, ValidationFailedError
17
47
 
@@ -19,7 +49,9 @@ __all__ = [
19
49
  "StaticValidator",
20
50
  "AppConfig",
21
51
  "ExitCode",
52
+ "LogLevel",
22
53
  "ValidationFailedError",
23
54
  "RuleParsingError",
24
55
  ]
25
- __version__ = "0.1.2"
56
+
57
+ __version__ = "0.2.1"
@@ -1,8 +1,17 @@
1
- """Enables running the validator as a module.
1
+ """Enables running the validator as a package.
2
2
 
3
3
  This file allows the package to be executed directly from the command line
4
- using `python -m code_validator`. It serves as the main entry point
5
- that invokes the command-line interface logic.
4
+ using the ``-m`` flag with Python (e.g., ``python -m code_validator``). It
5
+ serves as the primary entry point that finds and invokes the command-line
6
+ interface logic defined in the `cli` module.
7
+
8
+ Example:
9
+ You can run the validator package like this from the project root:
10
+
11
+ .. code-block:: bash
12
+
13
+ python -m code_validator path/to/solution.py path/to/rules.json
14
+
6
15
  """
7
16
 
8
17
  from .cli import run_from_cli
code_validator/cli.py CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  This module is responsible for parsing command-line arguments, setting up the
4
4
  application configuration, and orchestrating the main validation workflow. It acts
5
- as the primary entry point for user interaction.
5
+ as the primary entry point for user interaction when the tool is called from
6
+ the terminal.
7
+
8
+ The main function, `run_from_cli`, handles the entire application lifecycle,
9
+ including robust top-level error handling to ensure meaningful exit codes.
6
10
  """
7
11
 
8
12
  import argparse
@@ -19,70 +23,90 @@ from .output import Console, setup_logging
19
23
  def setup_arg_parser() -> argparse.ArgumentParser:
20
24
  """Creates and configures the argument parser for the CLI.
21
25
 
26
+ This function defines all positional and optional arguments that the
27
+ `validate-code` command accepts, including their types, help messages,
28
+ and default values.
29
+
22
30
  Returns:
23
- An instance of argparse.ArgumentParser with all arguments defined.
31
+ argparse.ArgumentParser: A fully configured parser instance ready to
32
+ process command-line arguments.
24
33
  """
25
34
  parser = argparse.ArgumentParser(
26
35
  prog="validate-code",
27
36
  description="Validates a Python source file against a set of JSON rules.",
28
37
  )
38
+
29
39
  parser.add_argument("solution_path", type=Path, help="Path to the Python solution file to validate.")
30
40
  parser.add_argument("rules_path", type=Path, help="Path to the JSON file with validation rules.")
41
+
31
42
  parser.add_argument(
32
- "-l",
33
- "--log-level",
43
+ "--log",
34
44
  type=LogLevel,
35
- choices=LogLevel,
36
- default=LogLevel.WARNING,
37
- help="Set the logging level (default: WARNING).",
45
+ default=LogLevel.ERROR,
46
+ help=("Set the logging level for stderr (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL). Default: ERROR."),
38
47
  )
39
- parser.add_argument("--silent", action="store_true", help="Suppress stdout output, show only logs.")
48
+ parser.add_argument(
49
+ "--quiet", action="store_true", help="Suppress all stdout output (validation errors and final verdict)."
50
+ )
51
+ parser.add_argument("--no-verdict", action="store_true", help="Suppress stdout output verdict, show failed rules.")
40
52
  parser.add_argument("--stop-on-first-fail", action="store_true", help="Stop after the first failed rule.")
41
- parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
53
+ parser.add_argument("--version", "-v", action="version", version=f"%(prog)s {__version__}")
42
54
  return parser
43
55
 
44
56
 
45
57
  def run_from_cli() -> None:
46
58
  """Runs the full application lifecycle from the command line.
47
59
 
48
- This function parses arguments, initializes logging and configuration,
49
- runs the validator, and handles all top-level exceptions, exiting with an
50
- appropriate exit code.
60
+ This is the main entry point for the `validate-code` script. It performs
61
+ the following steps:
62
+ 1. Parses command-line arguments.
63
+ 2. Initializes the logger, console, and configuration.
64
+ 3. Instantiates and runs the `StaticValidator`.
65
+ 4. Handles all top-level exceptions and exits with an appropriate status code.
66
+
67
+ Raises:
68
+ SystemExit: This function will always terminate the process with an
69
+ exit code defined in the `ExitCode` enum.
51
70
  """
52
71
  parser = setup_arg_parser()
53
72
  args = parser.parse_args()
54
73
 
55
- # 1. Setup environment
56
- logger = setup_logging(args.log_level)
57
- console = Console(logger, is_silent=args.silent)
74
+ logger = setup_logging(args.log)
75
+ console = Console(logger, is_quiet=args.quiet, show_verdict=not args.no_verdict)
76
+ console.print(f"Level of logging: {args.log}", level=LogLevel.DEBUG)
58
77
  config = AppConfig(
59
78
  solution_path=args.solution_path,
60
79
  rules_path=args.rules_path,
61
- log_level=args.log_level,
62
- is_silent=args.silent,
80
+ log_level=args.log,
81
+ is_quiet=args.quiet,
63
82
  stop_on_first_fail=args.stop_on_first_fail,
64
83
  )
84
+ console.print(f"Config is: {config}", level=LogLevel.TRACE)
65
85
 
66
- # 2. Run main logic with robust error handling
67
86
  try:
68
87
  console.print(f"Starting validation for: {config.solution_path}", level=LogLevel.INFO)
69
88
  validator = StaticValidator(config, console)
89
+
90
+ console.print("Start of validation..", level=LogLevel.TRACE)
70
91
  is_valid = validator.run()
92
+ console.print(f"End of validation with result: {is_valid = }", level=LogLevel.TRACE)
71
93
 
72
94
  if is_valid:
73
- console.print("Validation successful.", level=LogLevel.INFO)
95
+ console.print("Validation successful.", level=LogLevel.INFO, is_verdict=True)
74
96
  sys.exit(ExitCode.SUCCESS)
75
97
  else:
76
- console.print("Validation failed.", level=LogLevel.ERROR)
98
+ console.print("Validation failed.", level=LogLevel.INFO, is_verdict=True)
77
99
  sys.exit(ExitCode.VALIDATION_FAILED)
78
100
 
79
101
  except CodeValidatorError as e:
80
- console.print(str(e), level=LogLevel.CRITICAL)
102
+ console.print("Error: Internal Error of validator!", level=LogLevel.CRITICAL)
103
+ logger.exception(f"Traceback for CodeValidatorError: {e}")
81
104
  sys.exit(ExitCode.VALIDATION_FAILED)
82
105
  except FileNotFoundError as e:
83
- console.print(f"Error: File not found - {e.strerror}: {e.filename}", level=LogLevel.CRITICAL)
106
+ console.print(f"Error: File not found - {e.filename}!", level=LogLevel.CRITICAL)
107
+ logger.exception(f"Traceback for FileNotFoundError: {e}")
84
108
  sys.exit(ExitCode.FILE_NOT_FOUND)
85
109
  except Exception as e:
86
- console.print(f"An unexpected error occurred: {e}", level=LogLevel.CRITICAL)
87
- logger.exception("Traceback for unexpected error:")
110
+ console.print(f"An unexpected error occurred: {e.__class__.__name__}!", level=LogLevel.CRITICAL)
111
+ logger.exception(f"Traceback for unexpected error: {e}")
88
112
  sys.exit(ExitCode.UNEXPECTED_ERROR)
@@ -1,4 +1,9 @@
1
- """Provides utility functions for working with Python's Abstract Syntax Trees (AST)."""
1
+ """Provides utility functions for working with Python's Abstract Syntax Trees (AST).
2
+
3
+ This module contains helper functions that perform common operations on AST nodes,
4
+ such as enriching the tree with parent references. These utilities are used by
5
+ various components of the validator to simplify complex tree analysis.
6
+ """
2
7
 
3
8
  import ast
4
9
 
@@ -15,7 +20,6 @@ def enrich_ast_with_parents(tree: ast.Module) -> None:
15
20
  """
16
21
  for node in ast.walk(tree):
17
22
  for child in ast.iter_child_nodes(node):
18
- # Dynamically add a reference to the parent node.
19
23
  child.parent = node
20
24
 
21
25
 
@@ -1,10 +1,11 @@
1
- """Defines the core component interfaces using Protocols.
1
+ """Defines the core component interfaces for the validator using Protocols.
2
2
 
3
3
  This module establishes the fundamental "contracts" for the main architectural
4
- components of the validator: Rules, Selectors, and Constraints. By using
5
- Protocols, we ensure that any class conforming to these interfaces can be used
6
- interchangeably by the system's factories and core engine, enabling a flexible
7
- and decoupled plugin-style architecture.
4
+ components: Rules, Selectors, and Constraints. By using `typing.Protocol`, we
5
+ ensure that any class conforming to these interfaces can be used interchangeably
6
+ by the system's factories and core engine. This enables a flexible and
7
+ decoupled plugin-style architecture, where new components can be added without
8
+ modifying the core logic.
8
9
  """
9
10
 
10
11
  import ast
@@ -1,17 +1,18 @@
1
1
  """Contains factories for creating rule, selector, and constraint objects.
2
2
 
3
3
  This module implements the Factory Method design pattern to decouple the core
4
- validator engine from the concrete implementation of rules. Factories are
5
- responsible for parsing raw dictionary configurations from JSON and instantiating
6
- the appropriate handler classes.
4
+ validator engine from the concrete implementations of its components. Factories
5
+ are responsible for parsing raw dictionary configurations from the main JSON
6
+ rules file and instantiating the appropriate handler classes from the
7
+ `rules_library`.
7
8
  """
8
9
 
9
10
  import dataclasses
10
11
  from typing import Any, Type, TypeVar
11
12
 
12
- from ..config import ConstraintConfig, FullRuleCheck, FullRuleConfig, SelectorConfig, ShortRuleConfig
13
+ from ..config import ConstraintConfig, FullRuleCheck, FullRuleConfig, LogLevel, SelectorConfig, ShortRuleConfig
13
14
  from ..exceptions import RuleParsingError
14
- from ..output import Console
15
+ from ..output import Console, log_initialization
15
16
  from ..rules_library.basic_rules import CheckLinterRule, CheckSyntaxRule, FullRuleHandler
16
17
  from ..rules_library.constraint_logic import (
17
18
  IsForbiddenConstraint,
@@ -70,6 +71,7 @@ class RuleFactory:
70
71
  _constraint_factory (ConstraintFactory): A factory for creating constraint objects.
71
72
  """
72
73
 
74
+ @log_initialization(level=LogLevel.TRACE)
73
75
  def __init__(self, console: Console):
74
76
  """Initializes the RuleFactory.
75
77
 
@@ -100,8 +102,10 @@ class RuleFactory:
100
102
  required keys, or specifies an unknown type.
101
103
  """
102
104
  rule_id = rule_config.get("rule_id")
105
+ self._console.print(f"Start parsing rule ({rule_id}):\n{rule_config}", level=LogLevel.TRACE)
103
106
  try:
104
107
  if "type" in rule_config:
108
+ self._console.print(f"Rule {rule_id} is shorted rule - {rule_config['type']}", level=LogLevel.DEBUG)
105
109
  config = _create_dataclass_from_dict(ShortRuleConfig, rule_config)
106
110
  return self._create_short_rule(config)
107
111
 
@@ -109,20 +113,31 @@ class RuleFactory:
109
113
  raw_selector_cfg = rule_config["check"]["selector"]
110
114
  raw_constraint_cfg = rule_config["check"]["constraint"]
111
115
 
112
- selector = self._selector_factory.create(raw_selector_cfg)
113
- constraint = self._constraint_factory.create(raw_constraint_cfg)
114
-
115
116
  selector_cfg = _create_dataclass_from_dict(SelectorConfig, raw_selector_cfg)
116
117
  constraint_cfg = _create_dataclass_from_dict(ConstraintConfig, raw_constraint_cfg)
118
+
119
+ selector = self._selector_factory.create(selector_cfg)
120
+ constraint = self._constraint_factory.create(constraint_cfg)
121
+
122
+ self._console.print(
123
+ f"Rule {rule_id} is general rule with: selector - "
124
+ f"{selector_cfg.type}, constraint - {raw_constraint_cfg['type']}",
125
+ level=LogLevel.DEBUG,
126
+ )
127
+
117
128
  check_cfg = FullRuleCheck(selector=selector_cfg, constraint=constraint_cfg)
129
+ self._console.print(f"Create FullRuleCheck: {check_cfg}", level=LogLevel.TRACE)
130
+
118
131
  config = FullRuleConfig(
119
132
  rule_id=rule_config["rule_id"],
120
133
  message=rule_config["message"],
121
134
  check=check_cfg,
122
135
  is_critical=rule_config.get("is_critical", False),
123
136
  )
137
+ self._console.print(f"Create FullRuleConfig: {config}", level=LogLevel.TRACE)
124
138
  return FullRuleHandler(config, selector, constraint, self._console)
125
139
  else:
140
+ self._console.print(f"Invalid syntax of rule: {rule_id}", level=LogLevel.WARNING)
126
141
  raise RuleParsingError("Rule must contain 'type' or 'check' key.", rule_id)
127
142
  except (TypeError, KeyError, RuleParsingError) as e:
128
143
  raise RuleParsingError(f"Invalid config for rule '{rule_id}': {e}", rule_id) from e
@@ -164,21 +179,23 @@ class SelectorFactory:
164
179
  any state.
165
180
  """
166
181
 
182
+ @log_initialization(level=LogLevel.TRACE)
183
+ def __init__(self) -> None:
184
+ pass
185
+
167
186
  @staticmethod
168
- def create(selector_config: dict[str, Any]) -> Selector:
187
+ def create(config: SelectorConfig) -> Selector:
169
188
  """Creates a specific selector instance based on its type.
170
189
 
171
190
  This method uses the 'type' field from the selector configuration
172
191
  to determine which concrete Selector class to instantiate.
173
192
 
174
193
  Args:
175
- selector_config: The 'selector' block from a JSON rule.
194
+ config: The 'selector' block from a JSON rule.
176
195
 
177
196
  Returns:
178
197
  An instance of a class that conforms to the Selector protocol.
179
198
  """
180
- config = _create_dataclass_from_dict(SelectorConfig, selector_config)
181
-
182
199
  match config.type:
183
200
  case "function_def":
184
201
  return FunctionDefSelector(name=config.name, in_scope_config=config.in_scope)
@@ -209,21 +226,23 @@ class ConstraintFactory:
209
226
  to a list of AST nodes. This class uses a static `create` method.
210
227
  """
211
228
 
229
+ @log_initialization(level=LogLevel.TRACE)
230
+ def __init__(self) -> None:
231
+ pass
232
+
212
233
  @staticmethod
213
- def create(constraint_config: dict[str, Any]) -> Constraint:
234
+ def create(config: ConstraintConfig) -> Constraint:
214
235
  """Creates a specific constraint instance based on its type.
215
236
 
216
237
  This method uses the 'type' field from the constraint configuration
217
238
  to determine which concrete Constraint class to instantiate.
218
239
 
219
240
  Args:
220
- constraint_config: The 'constraint' block from a JSON rule.
241
+ config: The 'constraint' block from a JSON rule.
221
242
 
222
243
  Returns:
223
244
  An instance of a class that conforms to the Constraint protocol.
224
245
  """
225
- config = _create_dataclass_from_dict(ConstraintConfig, constraint_config)
226
-
227
246
  match config.type:
228
247
  case "is_required":
229
248
  return IsRequiredConstraint(count=config.count)
@@ -1,8 +1,10 @@
1
1
  """Provides functionality to find and isolate specific scopes within an AST.
2
2
 
3
- This module contains helper functions that are used by ScopedSelectors to narrow
4
- down their search area from the entire module to a specific function, class,
5
- or method, based on the `in_scope` configuration from a JSON rule.
3
+ This module contains a key helper function, `find_scope_node`, which is used
4
+ by `ScopedSelector` instances. Its purpose is to traverse the AST and return
5
+ a specific subtree (e.g., a function body or class body) based on the
6
+ `in_scope` configuration from a JSON rule. This allows rules to be applied
7
+ with high precision to specific parts of the source code.
6
8
  """
7
9
 
8
10
  import ast
code_validator/config.py CHANGED
@@ -25,6 +25,7 @@ class ExitCode(IntEnum):
25
25
  class LogLevel(StrEnum):
26
26
  """Defines the supported logging levels for the application."""
27
27
 
28
+ TRACE = "TRACE"
28
29
  DEBUG = "DEBUG"
29
30
  INFO = "INFO"
30
31
  WARNING = "WARNING"
@@ -34,18 +35,37 @@ class LogLevel(StrEnum):
34
35
 
35
36
  @dataclass(frozen=True)
36
37
  class AppConfig:
37
- """Stores the main application configuration from CLI arguments."""
38
+ """Stores the main application configuration from CLI arguments.
39
+
40
+ Attributes:
41
+ solution_path: The file path to the Python solution to be validated.
42
+ rules_path: The file path to the JSON rules file.
43
+ log_level: The minimum logging level for console output.
44
+ is_quiet: If True, suppresses all non-log output to stdout.
45
+ stop_on_first_fail: If True, halts validation after the first failed rule.
46
+ """
38
47
 
39
48
  solution_path: Path
40
49
  rules_path: Path
41
50
  log_level: LogLevel
42
- is_silent: bool
51
+ is_quiet: bool
43
52
  stop_on_first_fail: bool
44
53
 
45
54
 
46
55
  @dataclass(frozen=True)
47
56
  class SelectorConfig:
48
- """Represents the configuration for a Selector component from a JSON rule."""
57
+ """Represents the configuration for a Selector component from a JSON rule.
58
+
59
+ This dataclass captures all possible keys within the "selector" block
60
+ of a JSON validation rule.
61
+
62
+ Attributes:
63
+ type: The type of the selector to be used (e.g., "function_def").
64
+ name: A generic name parameter used by many selectors (e.g., the name
65
+ of a function, class, or module).
66
+ node_type: The AST node type name for the `ast_node` selector.
67
+ in_scope: The scope in which to apply the selector.
68
+ """
49
69
 
50
70
  type: str
51
71
  name: str | None = None
@@ -55,7 +75,21 @@ class SelectorConfig:
55
75
 
56
76
  @dataclass(frozen=True)
57
77
  class ConstraintConfig:
58
- """Represents the configuration for a Constraint component from a JSON rule."""
78
+ """Represents the configuration for a Constraint component from a JSON rule.
79
+
80
+ This dataclass captures all possible keys within the "constraint" block
81
+ of a JSON validation rule.
82
+
83
+ Attributes:
84
+ type: The type of the constraint to be applied (e.g., "is_required").
85
+ count: The exact number of nodes expected. Used by `is_required`.
86
+ parent_name: The expected parent class name. Used by `must_inherit_from`.
87
+ expected_type: The expected Python type name. Used by `must_be_type`.
88
+ allowed_names: A list of permitted names. Used by `name_must_be_in`.
89
+ allowed_values: A list of permitted literal values. Used by `value_must_be_in`.
90
+ names: A list of expected argument names. Used by `must_have_args`.
91
+ exact_match: A boolean flag for argument matching. Used by `must_have_args`.
92
+ """
59
93
 
60
94
  type: str
61
95
  count: int | None = None
@@ -69,7 +103,12 @@ class ConstraintConfig:
69
103
 
70
104
  @dataclass(frozen=True)
71
105
  class FullRuleCheck:
72
- """Represents the 'check' block within a full validation rule."""
106
+ """Represents the 'check' block within a full validation rule.
107
+
108
+ Attributes:
109
+ selector: The configuration for the selector component.
110
+ constraint: The configuration for the constraint component.
111
+ """
73
112
 
74
113
  selector: SelectorConfig
75
114
  constraint: ConstraintConfig
@@ -77,7 +116,14 @@ class FullRuleCheck:
77
116
 
78
117
  @dataclass(frozen=True)
79
118
  class ShortRuleConfig:
80
- """Represents a 'short' (pre-defined) validation rule from JSON."""
119
+ """Represents a 'short' (pre-defined) validation rule from JSON.
120
+
121
+ Attributes:
122
+ rule_id: The unique integer identifier for the rule.
123
+ type: The string identifier for the short rule (e.g., "check_syntax").
124
+ message: The error message to display if the rule fails.
125
+ params: A dictionary of optional parameters for the rule.
126
+ """
81
127
 
82
128
  rule_id: int
83
129
  type: str
@@ -87,7 +133,14 @@ class ShortRuleConfig:
87
133
 
88
134
  @dataclass(frozen=True)
89
135
  class FullRuleConfig:
90
- """Represents a 'full' (custom) validation rule with selector and constraint."""
136
+ """Represents a 'full' (custom) validation rule with a selector and constraint.
137
+
138
+ Attributes:
139
+ rule_id: The unique integer identifier for the rule.
140
+ message: The error message to display if the rule fails.
141
+ check: An object containing the selector and constraint configurations.
142
+ is_critical: If True, validation halts if this rule fails.
143
+ """
91
144
 
92
145
  rule_id: int
93
146
  message: str