python-code-validator 0.2.1__py3-none-any.whl → 0.3.0__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.
@@ -54,4 +54,4 @@ __all__ = [
54
54
  "RuleParsingError",
55
55
  ]
56
56
 
57
- __version__ = "0.2.1"
57
+ __version__ = "0.3.0"
code_validator/cli.py CHANGED
@@ -43,13 +43,22 @@ def setup_arg_parser() -> argparse.ArgumentParser:
43
43
  "--log",
44
44
  type=LogLevel,
45
45
  default=LogLevel.ERROR,
46
- help=("Set the logging level for stderr (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL). Default: ERROR."),
46
+ help="Set the logging level for stderr (TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL). Default: ERROR.",
47
47
  )
48
48
  parser.add_argument(
49
49
  "--quiet", action="store_true", help="Suppress all stdout output (validation errors and final verdict)."
50
50
  )
51
51
  parser.add_argument("--no-verdict", action="store_true", help="Suppress stdout output verdict, show failed rules.")
52
- parser.add_argument("--stop-on-first-fail", action="store_true", help="Stop after the first failed rule.")
52
+ parser.add_argument(
53
+ "--max-messages",
54
+ type=int,
55
+ default=0,
56
+ metavar="N",
57
+ help="Maximum number of error messages to display. 0 for no limit. Default: 0.",
58
+ )
59
+ parser.add_argument(
60
+ "-x", "--exit-on-first-error", action="store_true", help="Exit instantly on the first error found."
61
+ )
53
62
  parser.add_argument("--version", "-v", action="version", version=f"%(prog)s {__version__}")
54
63
  return parser
55
64
 
@@ -79,7 +88,8 @@ def run_from_cli() -> None:
79
88
  rules_path=args.rules_path,
80
89
  log_level=args.log,
81
90
  is_quiet=args.quiet,
82
- stop_on_first_fail=args.stop_on_first_fail,
91
+ exit_on_first_error=args.exit_on_first_error,
92
+ max_messages=args.max_messages,
83
93
  )
84
94
  console.print(f"Config is: {config}", level=LogLevel.TRACE)
85
95
 
@@ -95,18 +105,24 @@ def run_from_cli() -> None:
95
105
  console.print("Validation successful.", level=LogLevel.INFO, is_verdict=True)
96
106
  sys.exit(ExitCode.SUCCESS)
97
107
  else:
98
- console.print("Validation failed.", level=LogLevel.INFO, is_verdict=True)
108
+ console.print("Validation failed.", level=LogLevel.WARNING, is_verdict=True)
99
109
  sys.exit(ExitCode.VALIDATION_FAILED)
100
110
 
101
111
  except CodeValidatorError as e:
102
- console.print("Error: Internal Error of validator!", level=LogLevel.CRITICAL)
103
- logger.exception(f"Traceback for CodeValidatorError: {e}")
112
+ console.print(
113
+ f"Error: An internal validator error occurred: {e}", level=LogLevel.CRITICAL, show_user=True, exc_info=True
114
+ )
104
115
  sys.exit(ExitCode.VALIDATION_FAILED)
105
116
  except FileNotFoundError as e:
106
- console.print(f"Error: File not found - {e.filename}!", level=LogLevel.CRITICAL)
107
- logger.exception(f"Traceback for FileNotFoundError: {e}")
117
+ console.print(
118
+ f"Error: Input file not found: {e.filename}", level=LogLevel.CRITICAL, show_user=True, exc_info=True
119
+ )
108
120
  sys.exit(ExitCode.FILE_NOT_FOUND)
109
121
  except Exception as e:
110
- console.print(f"An unexpected error occurred: {e.__class__.__name__}!", level=LogLevel.CRITICAL)
111
- logger.exception(f"Traceback for unexpected error: {e}")
122
+ console.print(
123
+ f"Error: An unexpected error occurred: {e.__class__.__name__}. See logs for detailed traceback.",
124
+ level=LogLevel.CRITICAL,
125
+ show_user=True,
126
+ exc_info=True,
127
+ )
112
128
  sys.exit(ExitCode.UNEXPECTED_ERROR)
code_validator/config.py CHANGED
@@ -42,14 +42,16 @@ class AppConfig:
42
42
  rules_path: The file path to the JSON rules file.
43
43
  log_level: The minimum logging level for console output.
44
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.
45
+ exit_on_first_error: If True, halts validation after the first failed rule.
46
+ max_messages: Maximum number of error messages to display. 0 for no limit. Default: 0.
46
47
  """
47
48
 
48
49
  solution_path: Path
49
50
  rules_path: Path
50
51
  log_level: LogLevel
51
52
  is_quiet: bool
52
- stop_on_first_fail: bool
53
+ exit_on_first_error: bool
54
+ max_messages: int = 0
53
55
 
54
56
 
55
57
  @dataclass(frozen=True)
code_validator/core.py CHANGED
@@ -64,7 +64,7 @@ class StaticValidator:
64
64
  _source_code (str): The raw text content of the Python file being validated.
65
65
  _ast_tree (ast.Module | None): The Abstract Syntax Tree of the source code.
66
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.
67
+ _failed_rules (list[Rule]): A list of rules that contained IDs of failed checks during the run.
68
68
  """
69
69
 
70
70
  @log_initialization(level=LogLevel.DEBUG)
@@ -83,10 +83,10 @@ class StaticValidator:
83
83
  self._source_code: str = ""
84
84
  self._ast_tree: ast.Module | None = None
85
85
  self._rules: list[Rule] = []
86
- self._failed_rules: list[int] = []
86
+ self._failed_rules: list[Rule] = []
87
87
 
88
88
  @property
89
- def failed_rules_id(self) -> list[int]:
89
+ def failed_rules_id(self) -> list[Rule]:
90
90
  """list[int]: A list of rule IDs that failed during the last run."""
91
91
  return self._failed_rules
92
92
 
@@ -161,11 +161,54 @@ class StaticValidator:
161
161
  if getattr(rule.config, "type", None) == "check_syntax":
162
162
  self._console.print(rule.config.message, level=LogLevel.ERROR, show_user=True)
163
163
  self._console.print(f"Failed rule id: {rule.config.rule_id}", level=LogLevel.DEBUG)
164
- self._failed_rules.append(rule.config.rule_id)
164
+ self._failed_rules.append(rule)
165
165
  return False
166
166
  self._console.print(f"Syntax Error found: {e}", level=LogLevel.ERROR)
167
167
  return False
168
168
 
169
+ def _report_errors(self) -> None:
170
+ """Formats and prints collected validation errors to the console.
171
+
172
+ This method is responsible for presenting the final list of failed
173
+ rules to the user. It respects the `--max-messages` configuration
174
+ to avoid cluttering the terminal. If the number of found errors
175
+ exceeds the specified limit, it truncates the output and displays
176
+ a summary message indicating how many more errors were found.
177
+
178
+ The method retrieves the list of failed rules from `self._failed_rules`
179
+ and the display limit from `self._config`. All user-facing output is
180
+ channeled through the `self._console` object.
181
+
182
+ It performs the following steps:
183
+ 1. Checks if any errors were recorded. If not, it returns immediately.
184
+ 2. Determines the subset of errors to display based on the configured
185
+ `max_messages` limit (a value of 0 means no limit).
186
+ 3. Iterates through the selected error rules and prints their
187
+ failure messages.
188
+ 4. If the error list was truncated, prints a summary line, e.g.,
189
+ "... (5 more errors found)".
190
+ """
191
+ max_errors = self._config.max_messages
192
+ num_errors = len(self._failed_rules)
193
+
194
+ if num_errors == 0:
195
+ return None
196
+
197
+ errors_to_show = self._failed_rules
198
+ if 0 < max_errors < num_errors:
199
+ errors_to_show = self._failed_rules[:max_errors]
200
+
201
+ for rule in errors_to_show:
202
+ self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
203
+
204
+ if 0 < max_errors < num_errors:
205
+ remaining_count = num_errors - max_errors
206
+ self._console.print(
207
+ f"... ({remaining_count} more error{'s' if remaining_count > 1 else ''} found)",
208
+ level=LogLevel.WARNING,
209
+ show_user=True,
210
+ )
211
+
169
212
  def run(self) -> bool:
170
213
  """Runs the entire validation process from start to finish.
171
214
 
@@ -184,6 +227,7 @@ class StaticValidator:
184
227
  self._load_and_parse_rules()
185
228
 
186
229
  if not self._parse_ast_tree():
230
+ self._report_errors()
187
231
  return False
188
232
 
189
233
  self._console.print("Lead source code, load and parse rules and parsing code - PASS", level=LogLevel.DEBUG)
@@ -211,13 +255,18 @@ class StaticValidator:
211
255
  )
212
256
  is_passed = rule.execute(self._ast_tree, self._source_code)
213
257
  if not is_passed:
214
- self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
258
+ self._failed_rules.append(rule)
259
+ # self._console.print(rule.config.message, level=LogLevel.WARNING, show_user=True)
215
260
  self._console.print(f"Rule {rule.config.rule_id} - FAIL", level=LogLevel.INFO)
216
- self._failed_rules.append(rule.config.rule_id)
217
- if getattr(rule.config, "is_critical", False) or self._config.stop_on_first_fail:
261
+ if getattr(rule.config, "is_critical", False):
218
262
  self._console.print("Critical rule failed. Halting validation.", level=LogLevel.WARNING)
219
263
  break
264
+ elif self._config.exit_on_first_error:
265
+ self._console.print("Exiting on first error.", level=LogLevel.INFO)
266
+ break
220
267
  else:
221
268
  self._console.print(f"Rule {rule.config.rule_id} - PASS", level=LogLevel.INFO)
222
269
 
270
+ self._report_errors()
271
+
223
272
  return not self._failed_rules
code_validator/output.py CHANGED
@@ -210,6 +210,7 @@ class Console:
210
210
  level: LogLevel | Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = LogLevel.TRACE,
211
211
  is_verdict: bool = False,
212
212
  show_user: bool = False,
213
+ exc_info: bool = False,
213
214
  ) -> None:
214
215
  """Prints a message to stdout and logs it simultaneously.
215
216
 
@@ -228,9 +229,10 @@ class Console:
228
229
  show_user: If True and `is_verdict=False`, allows
229
230
  printing non-verdict messages to stdout. Defaults to
230
231
  False.
232
+ exc_info: If True this work as loggings.exception("<message>").
231
233
  """
232
234
  level_num = logging.getLevelName(level if isinstance(level, LogLevel) else level)
233
- self._logger.log(level_num, message, stacklevel=2)
235
+ self._logger.log(level_num, message, stacklevel=2, exc_info=exc_info)
234
236
 
235
237
  if (not self._is_quiet) and ((not is_verdict and show_user) or (is_verdict and self._show_verdict)):
236
238
  print(message, file=self._stdout)
@@ -1,9 +1,30 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-code-validator
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: A flexible, AST-based framework for static validation of Python code using declarative JSON rules.
5
5
  Author-email: Qu1nel <covach.qn@gmail.com>
6
- License-Expression: MIT
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Ivan Kovach
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
7
28
  Project-URL: Homepage, https://github.com/Qu1nel/PythonCodeValidator
8
29
  Project-URL: Documentation, https://pythoncodevalidator.readthedocs.io/en/latest/
9
30
  Project-URL: Bug Tracker, https://github.com/Qu1nel/PythonCodeValidator/issues
@@ -14,6 +35,7 @@ Classifier: Intended Audience :: Education
14
35
  Classifier: Programming Language :: Python :: 3
15
36
  Classifier: Programming Language :: Python :: 3.11
16
37
  Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: License :: OSI Approved :: MIT License
17
39
  Classifier: Operating System :: OS Independent
18
40
  Classifier: Topic :: Software Development :: Quality Assurance
19
41
  Classifier: Topic :: Software Development :: Testing
@@ -1,10 +1,10 @@
1
- code_validator/__init__.py,sha256=uag-AMj0a1I953aW7JLcxS6q0f_rueJWvO3AmGOWgUI,1721
1
+ code_validator/__init__.py,sha256=_C8AEkBsAbgA7PJ58v_K_2vuCXl_XtRrk_oMp6UEEvI,1721
2
2
  code_validator/__main__.py,sha256=Z41EoJqX03AI11gnku_Iwt6rP8SPUkYuxwN7P51qgLc,600
3
- code_validator/cli.py,sha256=-8pl9iv1ufRWiRKrY0vtk3ncZu6tFj6ZrKa0F-ruuZ4,4826
4
- code_validator/config.py,sha256=ELi19GC1YeLEZZbm49FO8a1URLBrZxAuOGkrHTK8FvE,5071
5
- code_validator/core.py,sha256=HyXXxECxsAgiXOl9tMZVqg0Xptx7nA-64IDVv_W4Svo,10117
3
+ code_validator/cli.py,sha256=f0B9Pmnj0oKuknhxo0seqbdgEedW8lA-WJuVujLZ9m0,5173
4
+ code_validator/config.py,sha256=h9bCeVc_gFMKDWWrexmIytuAivBednwBc7sO4c_Axrs,5196
5
+ code_validator/core.py,sha256=3w5Zi_2jyl4vl8IEN4Yekelqn8pi9Bv2VVego6SUGrw,12288
6
6
  code_validator/exceptions.py,sha256=XkiRNQ25FWJkjS2wBaUaKQcEL5WF9tN_HSV3tqJwDcE,1627
7
- code_validator/output.py,sha256=Saydp1D6JRIzHroK12K38lnoMZCJaIEDyPD1ZXgJFGA,9137
7
+ code_validator/output.py,sha256=wpr24W8m-viS5--RB9Z8dCxFEYlYxGaiwJ7pRMW4BWw,9268
8
8
  code_validator/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  code_validator/components/ast_utils.py,sha256=v0N3F58oxNMg7bUY6-8x0AeoLsxrzrp_bLTGNVr5EMU,1589
10
10
  code_validator/components/definitions.py,sha256=9WEXQ_i-S4Vega6HqOdjreZE_1cgJPC7guZodTKtzhc,3208
@@ -14,9 +14,9 @@ code_validator/rules_library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
14
14
  code_validator/rules_library/basic_rules.py,sha256=RGlHEDnd0Cn72ofbE8Y7wAl9h7iZR8reR80v2oiICTo,7118
15
15
  code_validator/rules_library/constraint_logic.py,sha256=-gN6GaIZvPfHqiLCzH5Jwuz6DcPaQKlepbMo2r5V2Yc,11026
16
16
  code_validator/rules_library/selector_nodes.py,sha256=wCAnQRSdEB8HM8avTy1wkJzyb6v2EQgHIPASXruiqbc,14733
17
- python_code_validator-0.2.1.dist-info/licenses/LICENSE,sha256=Lq69RwIO4Dge7OsjgAamJfYSDq2DWI2yzVYI1VX1s6c,1089
18
- python_code_validator-0.2.1.dist-info/METADATA,sha256=EKuyRnPMen6M3C5YgSWHXmI1BjOyTP_-ttXgIfZDGKc,10794
19
- python_code_validator-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- python_code_validator-0.2.1.dist-info/entry_points.txt,sha256=pw_HijiZyPxokVJHStTkGCwheTjukDomdk81JyHzv74,66
21
- python_code_validator-0.2.1.dist-info/top_level.txt,sha256=yowMDfABI5oqgW3hhTdec_7UHGeprkvc2BnqRzNbI5w,15
22
- python_code_validator-0.2.1.dist-info/RECORD,,
17
+ python_code_validator-0.3.0.dist-info/licenses/LICENSE,sha256=Lq69RwIO4Dge7OsjgAamJfYSDq2DWI2yzVYI1VX1s6c,1089
18
+ python_code_validator-0.3.0.dist-info/METADATA,sha256=rysi7-B63cRuzDUgE_bltaF06CDHBcdYDddB3isxNs4,12089
19
+ python_code_validator-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ python_code_validator-0.3.0.dist-info/entry_points.txt,sha256=pw_HijiZyPxokVJHStTkGCwheTjukDomdk81JyHzv74,66
21
+ python_code_validator-0.3.0.dist-info/top_level.txt,sha256=yowMDfABI5oqgW3hhTdec_7UHGeprkvc2BnqRzNbI5w,15
22
+ python_code_validator-0.3.0.dist-info/RECORD,,