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.
code_validator/core.py CHANGED
@@ -1,100 +1,223 @@
1
+ """The core engine of the Python Code Validator.
2
+
3
+ This module contains the main orchestrator class, `StaticValidator`, which is
4
+ responsible for managing the entire validation lifecycle. It loads the source
5
+ code and a set of JSON rules, then uses a factory-based component system to
6
+ execute each rule and report the results.
7
+
8
+ The core is designed to be decoupled from the specific implementations of rules,
9
+ selectors, and constraints, allowing for high extensibility.
10
+
11
+ Example:
12
+ To run a validation, you would typically use the CLI, but the core can also
13
+ be used programmatically:
14
+
15
+ .. code-block:: python
16
+
17
+ from code_validator import StaticValidator, AppConfig, LogLevel
18
+ from code_validator.output import Console, setup_logging
19
+ from pathlib import Path
20
+ import logging
21
+
22
+ logger = logging.getLogger(__name__)
23
+ console = Console(logger)
24
+ config = AppConfig(
25
+ solution_path=Path("path/to/solution.py"),
26
+ rules_path=Path("path/to/rules.json"),
27
+ log_level=LogLevel.INFO,
28
+ is_silent=False,
29
+ stop_on_first_fail=False
30
+ )
31
+
32
+ validator = StaticValidator(config, console)
33
+ is_valid = validator.run()
34
+
35
+ if is_valid:
36
+ print("Validation Passed!")
37
+ else:
38
+ print(f"Validation Failed. Errors in: {validator.failed_rules_id}")
39
+
40
+ """
41
+
1
42
  import ast
2
43
  import json
3
44
 
4
45
  from .components.ast_utils import enrich_ast_with_parents
5
46
  from .components.definitions import Rule
6
47
  from .components.factories import RuleFactory
7
- from .config import AppConfig, LogLevel
48
+ from .config import AppConfig, LogLevel, ShortRuleConfig
8
49
  from .exceptions import RuleParsingError
9
- from .output import Console
50
+ from .output import Console, log_initialization
10
51
 
11
52
 
12
53
  class StaticValidator:
13
- """Orchestrates the static validation process."""
54
+ """Orchestrates the static validation process.
55
+
56
+ This class is the main entry point for running a validation session. It
57
+ manages loading of source files and rules, parsing the code into an AST,
58
+ and iterating through the rules to execute them.
59
+
60
+ Attributes:
61
+ _config (AppConfig): The application configuration object.
62
+ _console (Console): The handler for all logging and stdout printing.
63
+ _rule_factory (RuleFactory): The factory responsible for creating rule objects.
64
+ _source_code (str): The raw text content of the Python file being validated.
65
+ _ast_tree (ast.Module | None): The Abstract Syntax Tree of the source code.
66
+ _rules (list[Rule]): A list of initialized, executable rule objects.
67
+ _failed_rules (list[int]): A list of rule IDs that failed during the run.
68
+ """
14
69
 
70
+ @log_initialization(level=LogLevel.DEBUG)
15
71
  def __init__(self, config: AppConfig, console: Console):
16
- """Initializes the validator with configuration and an output handler."""
72
+ """Initializes the StaticValidator.
73
+
74
+ Args:
75
+ config: An `AppConfig` object containing all necessary run
76
+ configurations, such as file paths and flags.
77
+ console: A `Console` object for handling all output.
78
+ """
17
79
  self._config = config
18
80
  self._console = console
81
+
19
82
  self._rule_factory = RuleFactory(self._console)
20
83
  self._source_code: str = ""
21
84
  self._ast_tree: ast.Module | None = None
22
- self._validation_rules: list[Rule] = []
85
+ self._rules: list[Rule] = []
23
86
  self._failed_rules: list[int] = []
24
87
 
25
88
  @property
26
89
  def failed_rules_id(self) -> list[int]:
27
- """Returns a list of rule IDs that failed during the last run."""
90
+ """list[int]: A list of rule IDs that failed during the last run."""
28
91
  return self._failed_rules
29
92
 
30
93
  def _load_source_code(self) -> None:
31
- """Loads the content of the student's solution file."""
32
- self._console.print(f"Reading source file: {self._config.solution_path}")
94
+ """Loads the content of the student's solution file into memory.
95
+
96
+ Raises:
97
+ FileNotFoundError: If the source file specified in the config does not exist.
98
+ RuleParsingError: If the source file cannot be read for any other reason.
99
+ """
100
+ self._console.print(f"Reading source file: {self._config.solution_path}", level=LogLevel.DEBUG)
33
101
  try:
34
102
  self._source_code = self._config.solution_path.read_text(encoding="utf-8")
103
+ self._console.print(f"Source code:\n{self._source_code}\n", level=LogLevel.TRACE)
35
104
  except FileNotFoundError:
105
+ self._console.print("During reading source file raised FileNotFound", level=LogLevel.TRACE)
36
106
  raise
37
107
  except Exception as e:
108
+ self._console.print("During reading source file raised some exception..", level=LogLevel.TRACE)
38
109
  raise RuleParsingError(f"Cannot read source file: {e}") from e
39
110
 
40
- def _parse_ast_tree(self) -> bool:
41
- """Parses the loaded source code into an AST."""
42
- self._console.print("Parsing Abstract Syntax Tree (AST)...")
43
- try:
44
- self._ast_tree = ast.parse(self._source_code)
45
- enrich_ast_with_parents(self._ast_tree)
46
- return True
47
- except SyntaxError as e:
48
- # Ищем правило check_syntax, чтобы вывести его кастомное сообщение
49
- for rule in self._validation_rules:
50
- if getattr(rule.config, "type", None) == "check_syntax":
51
- self._console.print(rule.config.message, level="ERROR")
52
- self._failed_rules.append(rule.config.rule_id)
53
- return False
54
- # Если такого правила нет, выводим стандартное сообщение
55
- self._console.print(f"Syntax Error found: {e}", level="ERROR")
56
- return False
57
-
58
111
  def _load_and_parse_rules(self) -> None:
59
- """Loads and parses the JSON file with validation rules."""
60
- self._console.print(f"Loading rules from: {self._config.rules_path}")
112
+ """Loads and parses the JSON file into executable Rule objects.
113
+
114
+ This method reads the JSON rules file, validates its basic structure,
115
+ and then uses the `RuleFactory` to instantiate a list of concrete
116
+ Rule objects.
117
+
118
+ Raises:
119
+ FileNotFoundError: If the rules file does not exist.
120
+ RuleParsingError: If the JSON is malformed or a rule configuration
121
+ is invalid.
122
+ """
123
+ self._console.print(f"Loading rules from: {self._config.rules_path}", level=LogLevel.DEBUG)
61
124
  try:
62
125
  rules_data = json.loads(self._config.rules_path.read_text(encoding="utf-8"))
126
+ self._console.print(f"Load rules:\n{rules_data}", level=LogLevel.TRACE)
63
127
  raw_rules = rules_data.get("validation_rules")
64
128
  if not isinstance(raw_rules, list):
65
129
  raise RuleParsingError("`validation_rules` key not found or is not a list.")
66
130
 
67
- self._validation_rules = [self._rule_factory.create(rule) for rule in raw_rules]
68
- self._console.print(f"Successfully parsed {len(self._validation_rules)} rules.")
131
+ self._console.print(f"Found {len(raw_rules)}.", level=LogLevel.DEBUG)
132
+ self._rules = [self._rule_factory.create(rule) for rule in raw_rules]
133
+ self._console.print(f"Successfully parsed {len(self._rules)} rules.", level=LogLevel.DEBUG)
69
134
  except json.JSONDecodeError as e:
135
+ self._console.print("During reading file of rules raised JsonDecodeError..", level=LogLevel.TRACE)
70
136
  raise RuleParsingError(f"Invalid JSON in rules file: {e}") from e
71
137
  except FileNotFoundError:
138
+ self._console.print("During reading file of rules raised FileNotFound", level=LogLevel.TRACE)
72
139
  raise
73
140
 
141
+ def _parse_ast_tree(self) -> bool:
142
+ """Parses the loaded source code into an AST and enriches it.
143
+
144
+ This method attempts to parse the source code. If successful, it calls
145
+ a helper to add parent references to each node in the tree, which is
146
+ crucial for many advanced checks. If a `SyntaxError` occurs, it
147
+ checks if a `check_syntax` rule was defined to provide a custom message.
148
+
149
+ Returns:
150
+ bool: True if parsing was successful, False otherwise.
151
+ """
152
+ self._console.print("Parsing Abstract Syntax Tree (AST)...", level=LogLevel.DEBUG)
153
+ try:
154
+ self._console.print("Start parse source code.", level=LogLevel.TRACE)
155
+ self._ast_tree = ast.parse(self._source_code)
156
+ enrich_ast_with_parents(self._ast_tree)
157
+ return True
158
+ except SyntaxError as e:
159
+ self._console.print("In source code SyntaxError..", level=LogLevel.TRACE)
160
+ for rule in self._rules:
161
+ if getattr(rule.config, "type", None) == "check_syntax":
162
+ self._console.print(rule.config.message, level=LogLevel.ERROR, show_user=True)
163
+ self._console.print(f"Failed rule id: {rule.config.rule_id}", level=LogLevel.DEBUG)
164
+ self._failed_rules.append(rule.config.rule_id)
165
+ return False
166
+ self._console.print(f"Syntax Error found: {e}", level=LogLevel.ERROR)
167
+ return False
168
+
74
169
  def run(self) -> bool:
75
- """Runs the entire validation process."""
170
+ """Runs the entire validation process from start to finish.
171
+
172
+ This is the main public method of the class. It orchestrates the
173
+ sequence of loading, parsing, and rule execution.
174
+
175
+ Returns:
176
+ bool: True if all validation rules passed, False otherwise.
177
+
178
+ Raises:
179
+ RuleParsingError: Propagated from loading/parsing steps.
180
+ FileNotFoundError: Propagated from loading steps.
181
+ """
76
182
  try:
77
183
  self._load_source_code()
78
- self._load_and_parse_rules() # Загружаем правила до парсинга AST
184
+ self._load_and_parse_rules()
79
185
 
80
186
  if not self._parse_ast_tree():
81
187
  return False
82
188
 
83
- except (FileNotFoundError, RuleParsingError):
189
+ self._console.print("Lead source code, load and parse rules and parsing code - PASS", level=LogLevel.DEBUG)
190
+
191
+ except (FileNotFoundError, RuleParsingError) as e:
192
+ self._console.print(
193
+ f"In method `run` of 'StaticValidator' raised exception {e.__class__.__name__}", level=LogLevel.WARNING
194
+ )
84
195
  raise
85
196
 
86
- for rule in self._validation_rules:
87
- # check_syntax уже обработан в _parse_ast_tree, пропускаем его
197
+ self._console.print("Starting check rules..", level=LogLevel.DEBUG)
198
+ for rule in self._rules:
88
199
  if getattr(rule.config, "type", None) == "check_syntax":
89
200
  continue
90
201
 
91
- self._console.print(f"Executing rule: {rule.config.rule_id}", level=LogLevel.DEBUG)
202
+ self._console.print(
203
+ f"Executing rule: {rule.config.rule_id}"
204
+ + (
205
+ f" [{rule.config.check.selector.type}, {rule.config.check.constraint.type}, "
206
+ f"is_critical={rule.config.is_critical}]"
207
+ if not isinstance(rule.config, ShortRuleConfig)
208
+ else ""
209
+ ),
210
+ level=LogLevel.INFO,
211
+ )
92
212
  is_passed = rule.execute(self._ast_tree, self._source_code)
93
213
  if not is_passed:
94
- self._console.print(rule.config.message, level="ERROR")
214
+ self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
215
+ self._console.print(f"Rule {rule.config.rule_id} - FAIL", level=LogLevel.INFO)
95
216
  self._failed_rules.append(rule.config.rule_id)
96
217
  if getattr(rule.config, "is_critical", False) or self._config.stop_on_first_fail:
97
- self._console.print("Critical rule failed. Halting validation.", level="WARNING")
218
+ self._console.print("Critical rule failed. Halting validation.", level=LogLevel.WARNING)
98
219
  break
220
+ else:
221
+ self._console.print(f"Rule {rule.config.rule_id} - PASS", level=LogLevel.INFO)
99
222
 
100
223
  return not self._failed_rules
code_validator/output.py CHANGED
@@ -7,12 +7,50 @@ controlled via configuration (e.g., log levels, silent mode).
7
7
 
8
8
  import logging
9
9
  import sys
10
- from typing import Literal
10
+ from functools import wraps
11
+ from typing import Callable, Concatenate, Literal, ParamSpec, TypeVar
11
12
 
12
13
  from .config import LogLevel
13
14
 
14
- LOG_FORMAT = "%(asctime)s | %(filename)-15s | %(funcName)-15s (%(lineno)-3s) | [%(levelname)s] - %(message)s"
15
15
  DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
16
+ LOG_FORMAT = (
17
+ "{asctime}.{msecs:03.0f} | "
18
+ "{levelname:^8} | "
19
+ "[{processName}({process})/{threadName}({thread})] | "
20
+ "{filename}:{funcName}:{lineno} | "
21
+ "{message}"
22
+ )
23
+
24
+ TRACE_LEVEL_NUM = 5
25
+ logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
26
+
27
+
28
+ def trace(self, message, *args, **kws):
29
+ """Logs a message with TRACE level (below DEBUG).
30
+
31
+ This method allows logging messages with a custom TRACE level,
32
+ defined at level number 5. It only emits the log if the logger
33
+ is enabled for this level.
34
+
35
+ To enable usage, attach this method to the `logging.Logger` class:
36
+
37
+ logging.Logger.trace = trace
38
+
39
+ Args:
40
+ self: logger instance.
41
+ message: The log message format string.
42
+ *args: Arguments to be merged into the message format string.
43
+ **kws: Optional keyword arguments passed to the logger,
44
+ e.g., `exc_info`, `stacklevel`, or `extra`.
45
+
46
+ Returns:
47
+ None
48
+ """
49
+ if self.isEnabledFor(TRACE_LEVEL_NUM):
50
+ self._log(TRACE_LEVEL_NUM, message, args, **kws)
51
+
52
+
53
+ logging.Logger.trace = trace
16
54
 
17
55
 
18
56
  def setup_logging(log_level: LogLevel) -> logging.Logger:
@@ -27,8 +65,75 @@ def setup_logging(log_level: LogLevel) -> logging.Logger:
27
65
  Returns:
28
66
  The configured root logger instance.
29
67
  """
30
- logging.basicConfig(level=log_level, format=LOG_FORMAT, datefmt=DATE_FORMAT)
31
- return logging.getLogger()
68
+ formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=DATE_FORMAT, style="{")
69
+
70
+ handler = logging.StreamHandler(sys.stderr)
71
+ handler.setFormatter(formatter)
72
+
73
+ root_logger = logging.getLogger()
74
+ root_logger.setLevel(log_level)
75
+
76
+ if root_logger.hasHandlers():
77
+ root_logger.handlers.clear()
78
+
79
+ root_logger.addHandler(handler)
80
+
81
+ return root_logger
82
+
83
+
84
+ P = ParamSpec("P")
85
+ T_self = TypeVar("T_self")
86
+
87
+
88
+ def log_initialization(
89
+ level: LogLevel | Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = LogLevel.TRACE,
90
+ start_message: str = "Initializing {class_name}...",
91
+ end_message: str = "{class_name} initialized.",
92
+ ) -> Callable[[Callable[Concatenate[T_self, P], None]], Callable[Concatenate[T_self, P], None]]:
93
+ """Decorator factory for logging the initialization of a class instance.
94
+
95
+ This decorator wraps a class's `__init__` method to automatically log
96
+ messages before and after the constructor's execution. It helps in
97
+ observing the lifecycle of objects, especially complex ones, without
98
+ cluttering the `__init__` method itself.
99
+
100
+ The log messages can include a `{class_name}` placeholder, which will
101
+ be replaced by the actual name of the class being initialized.
102
+
103
+ Args:
104
+ level: The logging level (e.g., `LogLevel.DEBUG`, `LogLevel.INFO`)
105
+ at which the messages should be logged.
106
+ start_message: the message string to log immediately before the
107
+ `__init__` method is called. This string can contain the
108
+ `{class_name}` placeholder.
109
+ end_message: The message string to log immediately after the
110
+ `__init__` method completes its execution. This string can
111
+ contain the `{class_name}` placeholder.
112
+
113
+ Returns:
114
+ A decorator function that can be applied to an `__init__` method
115
+ of a class.
116
+ """
117
+
118
+ def decorator(init_method: Callable[Concatenate[T_self, P], None]) -> Callable[Concatenate[T_self, P], None]:
119
+ """The actual decorator function."""
120
+
121
+ @wraps(init_method)
122
+ def wrapper(self: T_self, *args: P.args, **kwargs: P.kwargs) -> None:
123
+ """The wrapper function that adds logging around __init__."""
124
+ class_name = self.__class__.__name__
125
+ logger = logging.getLogger(self.__class__.__module__)
126
+ level_num = logging.getLevelName(level if isinstance(level, LogLevel) else level)
127
+
128
+ logger.log(level_num, start_message.format(class_name=class_name))
129
+ result = init_method(self, *args, **kwargs)
130
+ logger.log(level_num, end_message.format(class_name=class_name))
131
+
132
+ return result
133
+
134
+ return wrapper
135
+
136
+ return decorator
32
137
 
33
138
 
34
139
  class Console:
@@ -40,7 +145,7 @@ class Console:
40
145
 
41
146
  Attributes:
42
147
  _logger (logging.Logger): The logger instance used for all log records.
43
- _is_silent (bool): A flag to suppress printing to stdout.
148
+ _is_quiet (bool): A flag to suppress printing to stdout.
44
149
  _stdout (TextIO): The stream to write messages to (defaults to sys.stdout).
45
150
 
46
151
  Example:
@@ -53,22 +158,58 @@ class Console:
53
158
  This is a warning.
54
159
  """
55
160
 
56
- def __init__(self, logger: logging.Logger, *, is_silent: bool = False):
161
+ @log_initialization(level=LogLevel.TRACE)
162
+ def __init__(self, logger: logging.Logger, *, is_quiet: bool = False, show_verdict: bool = True):
57
163
  """Initializes the Console handler.
58
164
 
59
165
  Args:
60
166
  logger: The configured logger instance to use for logging.
61
- is_silent: If True, suppresses output to stdout. Defaults to False.
167
+ is_quiet: If True, suppresses output to stdout. Defaults to False.
168
+ show_verdict: If False, suppresses showing verdicts. Default to True
62
169
  """
63
170
  self._logger = logger
64
- self._is_silent = is_silent
171
+ self._is_quiet = is_quiet
172
+ self._show_verdict = show_verdict
65
173
  self._stdout = sys.stdout
66
174
 
175
+ def should_print(self, is_verdict: bool, show_user: bool) -> bool:
176
+ """Decides whether a message should be printed to stdout based on console flags.
177
+
178
+ Quiet mode (Q) suppresses all output if enabled. For non-verdict messages (¬V),
179
+ printing is allowed only when show_user (O) is True. For verdict messages (V),
180
+ printing is allowed only when show_verdict (S) is True.
181
+
182
+ Mathematically:
183
+ F = ¬Q ∧ ( (¬V ∧ O) ∨ (V ∧ S) )
184
+ where
185
+ Q = self._is_quiet,
186
+ V = is_verdict,
187
+ S = self._show_verdict,
188
+ O = show_user.
189
+
190
+ Proof sketch:
191
+ 1. If Q is True, then ¬Q = False ⇒ F = False (silent mode).
192
+ 2. If Q is False, split on V:
193
+ a. V = False ⇒ F = ¬Q ∧ O;
194
+ b. V = True ⇒ F = ¬Q ∧ S.
195
+ 3. Combine branches into F = ¬Q ∧ ( (¬V ∧ O) ∨ (V ∧ S) ).
196
+
197
+ Args:
198
+ is_verdict: True if this message is a verdict; controls branch V.
199
+ show_user: True if non-verdict messages should be printed; controls branch O.
200
+
201
+ Returns:
202
+ True if and only if the combined condition F holds, i.e. the message may be printed.
203
+ """
204
+ return not self._is_quiet and ((not is_verdict and show_user) or (is_verdict and self._show_verdict))
205
+
67
206
  def print(
68
207
  self,
69
208
  message: str,
70
209
  *,
71
- level: LogLevel | Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = LogLevel.INFO,
210
+ level: LogLevel | Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = LogLevel.TRACE,
211
+ is_verdict: bool = False,
212
+ show_user: bool = False,
72
213
  ) -> None:
73
214
  """Prints a message to stdout and logs it simultaneously.
74
215
 
@@ -80,11 +221,16 @@ class Console:
80
221
  message: The string message to be displayed and logged.
81
222
  level: The logging level for the message. Accepts both LogLevel
82
223
  enum members and their string representations.
83
- Defaults to LogLevel.INFO.
224
+ Defaults to LogLevel.TRACE.
225
+ is_verdict: If True, this message is considered a
226
+ verdict and its printing is controlled by the
227
+ console’s `show_verdict` flag. Defaults to False.
228
+ show_user: If True and `is_verdict=False`, allows
229
+ printing non-verdict messages to stdout. Defaults to
230
+ False.
84
231
  """
85
- level_str = level.value.lower() if isinstance(level, LogLevel) else level.lower()
86
- log_method = getattr(self._logger, level_str)
87
- log_method(message)
232
+ level_num = logging.getLevelName(level if isinstance(level, LogLevel) else level)
233
+ self._logger.log(level_num, message, stacklevel=2)
88
234
 
89
- if not self._is_silent:
235
+ if (not self._is_quiet) and ((not is_verdict and show_user) or (is_verdict and self._show_verdict)):
90
236
  print(message, file=self._stdout)
@@ -1,10 +1,10 @@
1
- # src/code_validator/rules_library/basic_rules.py
2
-
3
1
  """Contains concrete implementations of executable validation rules.
4
2
 
5
3
  This module defines the handler classes for both "short" (pre-defined) and
6
- "full" (custom selector/constraint) rules. Each class implements the `Rule`
7
- protocol and encapsulates the logic for a specific type of validation check.
4
+ "full" (custom selector/constraint) rules. Each class in this module implements
5
+ the `Rule` protocol from `definitions.py` and encapsulates the logic for a
6
+ specific type of validation check. The `RuleFactory` uses these classes to
7
+ instantiate the correct handler for each rule defined in a JSON file.
8
8
  """
9
9
 
10
10
  import ast
@@ -13,7 +13,7 @@ import sys
13
13
 
14
14
  from ..components.definitions import Constraint, Rule, Selector
15
15
  from ..config import FullRuleConfig, ShortRuleConfig
16
- from ..output import Console, LogLevel
16
+ from ..output import Console, LogLevel, log_initialization
17
17
 
18
18
 
19
19
  class CheckSyntaxRule(Rule):
@@ -27,6 +27,7 @@ class CheckSyntaxRule(Rule):
27
27
  formally defined in the JSON configuration.
28
28
  """
29
29
 
30
+ @log_initialization(level=LogLevel.TRACE)
30
31
  def __init__(self, config: ShortRuleConfig, console: Console):
31
32
  """Initializes the syntax check rule handler.
32
33
 
@@ -45,7 +46,7 @@ class CheckSyntaxRule(Rule):
45
46
  Returns:
46
47
  Always returns True.
47
48
  """
48
- self._console.print(f"Rule {self.config.rule_id}: Syntax is valid.", level=LogLevel.DEBUG)
49
+ self._console.print(f"Rule {self.config.rule_id}: Syntax is valid.", level=LogLevel.INFO)
49
50
  return True
50
51
 
51
52
 
@@ -57,6 +58,7 @@ class CheckLinterRule(Rule):
57
58
  configurable via the 'params' field in the JSON rule.
58
59
  """
59
60
 
61
+ @log_initialization(level=LogLevel.TRACE)
60
62
  def __init__(self, config: ShortRuleConfig, console: Console):
61
63
  """Initializes a PEP8 linter check rule handler."""
62
64
  self.config = config
@@ -77,10 +79,10 @@ class CheckLinterRule(Rule):
77
79
  True if no PEP8 violations are found, False otherwise.
78
80
  """
79
81
  if not source_code:
80
- self._console.print("Source code is empty, skipping PEP8 check.", level="WARNING")
82
+ self._console.print("Source code is empty, skipping PEP8 check.", level=LogLevel.WARNING)
81
83
  return True
82
84
 
83
- self._console.print(f"Rule {self.config.rule_id}: Running flake8 linter...", level="DEBUG")
85
+ self._console.print(f"Rule {self.config.rule_id}: Running PEP8 linter...", level=LogLevel.INFO)
84
86
 
85
87
  params = self.config.params
86
88
  args = [sys.executable, "-m", "flake8", "-"]
@@ -90,6 +92,8 @@ class CheckLinterRule(Rule):
90
92
  elif ignore_list := params.get("ignore"):
91
93
  args.append(f"--ignore={','.join(ignore_list)}")
92
94
 
95
+ self._console.print(f"Arguments for flake8: {args}", level=LogLevel.TRACE)
96
+
93
97
  try:
94
98
  process = subprocess.run(
95
99
  args,
@@ -102,19 +106,21 @@ class CheckLinterRule(Rule):
102
106
 
103
107
  if process.returncode != 0 and process.stdout:
104
108
  linter_output = process.stdout.strip()
105
- self._console.print(f"Flake8 found issues:\n{linter_output}", level="DEBUG")
109
+ self._console.print(f"Flake8 found issues:\n{linter_output}", level=LogLevel.WARNING, show_user=True)
106
110
  return False
107
111
  elif process.returncode != 0:
108
- self._console.print(f"Flake8 exited with code {process.returncode}:\n{process.stderr}", level="ERROR")
112
+ self._console.print(
113
+ f"Flake8 exited with code {process.returncode}:\n{process.stderr}", level=LogLevel.ERROR
114
+ )
109
115
  return False
110
116
 
111
- self._console.print("PEP8 check passed.", level="DEBUG")
117
+ self._console.print("PEP8 check passed.", level=LogLevel.INFO)
112
118
  return True
113
119
  except FileNotFoundError:
114
- self._console.print("flake8 not found. Is it installed in the venv?", level="CRITICAL")
120
+ self._console.print("flake8 not found. Is it installed in the venv?", level=LogLevel.CRITICAL)
115
121
  return False
116
122
  except Exception as e:
117
- self._console.print(f"An unexpected error occurred while running flake8: {e}", level="CRITICAL")
123
+ self._console.print(f"An unexpected error occurred while running flake8: {e}", level=LogLevel.CRITICAL)
118
124
  return False
119
125
 
120
126
 
@@ -132,6 +138,7 @@ class FullRuleHandler(Rule):
132
138
  _console (Console): The console handler for logging.
133
139
  """
134
140
 
141
+ @log_initialization(level=LogLevel.TRACE)
135
142
  def __init__(self, config: FullRuleConfig, selector: Selector, constraint: Constraint, console: Console):
136
143
  """Initializes a full rule handler.
137
144
 
@@ -157,11 +164,11 @@ class FullRuleHandler(Rule):
157
164
  The boolean result of applying the constraint to the selected nodes.
158
165
  """
159
166
  if not tree:
160
- self._console.print("AST not available, skipping rule.", level="WARNING")
167
+ self._console.print("AST not available, skipping rule.", level=LogLevel.WARNING)
161
168
  return True
162
169
 
163
- self._console.print(f"Applying selector: {self._selector.__class__.__name__}", level="DEBUG")
170
+ self._console.print(f"Applying selector: {self._selector.__class__.__name__}", level=LogLevel.TRACE)
164
171
  selected_nodes = self._selector.select(tree)
165
172
 
166
- self._console.print(f"Applying constraint: {self._constraint.__class__.__name__}", level="DEBUG")
173
+ self._console.print(f"Applying constraint: {self._constraint.__class__.__name__}", level=LogLevel.TRACE)
167
174
  return self._constraint.check(selected_nodes)