python-code-validator 0.2.1__tar.gz → 0.3.0__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 (30) hide show
  1. {python_code_validator-0.2.1/src/python_code_validator.egg-info → python_code_validator-0.3.0}/PKG-INFO +24 -2
  2. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/pyproject.toml +3 -3
  3. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/__init__.py +1 -1
  4. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/cli.py +26 -10
  5. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/config.py +4 -2
  6. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/core.py +56 -7
  7. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/output.py +3 -1
  8. {python_code_validator-0.2.1 → python_code_validator-0.3.0/src/python_code_validator.egg-info}/PKG-INFO +24 -2
  9. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/tests/test_validator.py +11 -9
  10. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/LICENSE +0 -0
  11. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/README.md +0 -0
  12. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/setup.cfg +0 -0
  13. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/__main__.py +0 -0
  14. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/components/__init__.py +0 -0
  15. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/components/ast_utils.py +0 -0
  16. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/components/definitions.py +0 -0
  17. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/components/factories.py +0 -0
  18. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/components/scope_handler.py +0 -0
  19. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/exceptions.py +0 -0
  20. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/rules_library/__init__.py +0 -0
  21. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/rules_library/basic_rules.py +0 -0
  22. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/rules_library/constraint_logic.py +0 -0
  23. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/code_validator/rules_library/selector_nodes.py +0 -0
  24. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/python_code_validator.egg-info/SOURCES.txt +0 -0
  25. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/python_code_validator.egg-info/dependency_links.txt +0 -0
  26. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/python_code_validator.egg-info/entry_points.txt +0 -0
  27. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/python_code_validator.egg-info/requires.txt +0 -0
  28. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/src/python_code_validator.egg-info/top_level.txt +0 -0
  29. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/tests/test_components.py +0 -0
  30. {python_code_validator-0.2.1 → python_code_validator-0.3.0}/tests/test_scope_handler.py +0 -0
@@ -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
@@ -10,14 +10,13 @@ build-backend = "setuptools.build_meta"
10
10
  # ==============================================================================
11
11
  [project]
12
12
  name = "python-code-validator"
13
- version = "0.2.1"
13
+ version = "0.3.0"
14
14
  description = "A flexible, AST-based framework for static validation of Python code using declarative JSON rules."
15
15
  keywords = ["validation", "linter", "static analysis", "testing", "education", "ast"]
16
16
  authors = [{ name = "Qu1nel", email = "covach.qn@gmail.com" }]
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.11"
19
- license-files = ["LICEN[CS]E*"]
20
- license = "MIT"
19
+ license = { file = "LICENSE" }
21
20
  classifiers = [
22
21
  "Development Status :: 4 - Beta",
23
22
  "Intended Audience :: Developers",
@@ -25,6 +24,7 @@ classifiers = [
25
24
  "Programming Language :: Python :: 3",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
27
+ "License :: OSI Approved :: MIT License",
28
28
  "Operating System :: OS Independent",
29
29
  "Topic :: Software Development :: Quality Assurance",
30
30
  "Topic :: Software Development :: Testing",
@@ -54,4 +54,4 @@ __all__ = [
54
54
  "RuleParsingError",
55
55
  ]
56
56
 
57
- __version__ = "0.2.1"
57
+ __version__ = "0.3.0"
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -24,7 +24,7 @@ class TestStaticValidatorIntegration(unittest.TestCase):
24
24
  rules_path=FIXTURES_DIR / rules_file,
25
25
  log_level=LogLevel.CRITICAL,
26
26
  is_quiet=True,
27
- stop_on_first_fail=False,
27
+ exit_on_first_error=False,
28
28
  )
29
29
  validator = StaticValidator(config, self.console)
30
30
  return validator.run()
@@ -81,7 +81,7 @@ class TestStaticValidatorIntegration(unittest.TestCase):
81
81
  rules_path=rules_path,
82
82
  log_level=LogLevel.CRITICAL,
83
83
  is_quiet=True,
84
- stop_on_first_fail=False,
84
+ exit_on_first_error=False,
85
85
  )
86
86
  validator = StaticValidator(config, self.console)
87
87
 
@@ -133,7 +133,7 @@ class TestStaticValidatorIntegration(unittest.TestCase):
133
133
 
134
134
  self.assertFalse(result)
135
135
 
136
- def test_stop_on_first_fail_works(self):
136
+ def test_exit_on_first_error_works(self):
137
137
  """Tests that the validator stops after the first failure if flag is set."""
138
138
  rules = {
139
139
  "validation_rules": [
@@ -165,7 +165,7 @@ class TestStaticValidatorIntegration(unittest.TestCase):
165
165
  rules_path=rules_path,
166
166
  log_level=LogLevel.CRITICAL,
167
167
  is_quiet=True,
168
- stop_on_first_fail=True,
168
+ exit_on_first_error=True,
169
169
  )
170
170
  validator = StaticValidator(config, self.console)
171
171
  validator.run()
@@ -173,7 +173,8 @@ class TestStaticValidatorIntegration(unittest.TestCase):
173
173
 
174
174
  # Проверяем, что провалилось только одно правило, а не два
175
175
  self.assertEqual(len(validator.failed_rules_id), 1)
176
- self.assertEqual(validator.failed_rules_id[0], 1)
176
+ # noinspection PyTypeChecker
177
+ self.assertEqual(validator.failed_rules_id[0].config.rule_id, 1)
177
178
 
178
179
  def test_unknown_rule_type_raises_error(self):
179
180
  """Tests that an unknown rule type in JSON raises RuleParsingError."""
@@ -256,7 +257,7 @@ class TestValidatorRobustness(unittest.TestCase):
256
257
  rules_path=FIXTURES_DIR / rules_file,
257
258
  log_level=LogLevel.CRITICAL,
258
259
  is_quiet=True,
259
- stop_on_first_fail=False,
260
+ exit_on_first_error=False,
260
261
  )
261
262
  validator = StaticValidator(config, self.console)
262
263
  return validator.run()
@@ -273,7 +274,7 @@ class TestValidatorRobustness(unittest.TestCase):
273
274
  rules_path=self.valid_rules_path,
274
275
  log_level=LogLevel.CRITICAL,
275
276
  is_quiet=True,
276
- stop_on_first_fail=False,
277
+ exit_on_first_error=False,
277
278
  )
278
279
  # Ожидаем, что проверка провалится (т.к. в пустом файле нет нужных функций),
279
280
  # но сама программа не упадет с ошибкой.
@@ -287,7 +288,7 @@ class TestValidatorRobustness(unittest.TestCase):
287
288
  rules_path=self.valid_rules_path,
288
289
  log_level=LogLevel.CRITICAL,
289
290
  is_quiet=True,
290
- stop_on_first_fail=False,
291
+ exit_on_first_error=False,
291
292
  )
292
293
  # Проверяем, что вызов .run() вызывает именно FileNotFoundError
293
294
  with self.assertRaises(FileNotFoundError):
@@ -301,7 +302,7 @@ class TestValidatorRobustness(unittest.TestCase):
301
302
  rules_path=FIXTURES_DIR / "malformed.json",
302
303
  log_level=LogLevel.CRITICAL,
303
304
  is_quiet=True,
304
- stop_on_first_fail=False,
305
+ exit_on_first_error=False,
305
306
  )
306
307
  with self.assertRaises(RuleParsingError):
307
308
  validator = StaticValidator(config, self.console)
@@ -328,6 +329,7 @@ class TestValidatorRobustness(unittest.TestCase):
328
329
  }
329
330
  rules_path = FIXTURES_DIR / "temp_arcade_rules.json"
330
331
  with open(rules_path, "w", encoding="utf-8") as f:
332
+ # noinspection PyTypeChecker
331
333
  json.dump(arcade_rules, f)
332
334
 
333
335
  result = self.run_validator("p09_arcade_app.py", str(rules_path))