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.
- code_validator/__init__.py +1 -1
- code_validator/cli.py +26 -10
- code_validator/config.py +4 -2
- code_validator/core.py +56 -7
- code_validator/output.py +3 -1
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/METADATA +24 -2
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/RECORD +11 -11
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/WHEEL +0 -0
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/top_level.txt +0 -0
code_validator/__init__.py
CHANGED
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=
|
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(
|
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
|
-
|
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.
|
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(
|
103
|
-
|
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(
|
107
|
-
|
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(
|
111
|
-
|
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
|
-
|
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
|
-
|
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[
|
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[
|
86
|
+
self._failed_rules: list[Rule] = []
|
87
87
|
|
88
88
|
@property
|
89
|
-
def failed_rules_id(self) -> list[
|
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
|
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.
|
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
|
-
|
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.
|
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
|
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=
|
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
|
4
|
-
code_validator/config.py,sha256=
|
5
|
-
code_validator/core.py,sha256=
|
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=
|
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.
|
18
|
-
python_code_validator-0.
|
19
|
-
python_code_validator-0.
|
20
|
-
python_code_validator-0.
|
21
|
-
python_code_validator-0.
|
22
|
-
python_code_validator-0.
|
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,,
|
File without changes
|
{python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/entry_points.txt
RENAMED
File without changes
|
{python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
{python_code_validator-0.2.1.dist-info → python_code_validator-0.3.0.dist-info}/top_level.txt
RENAMED
File without changes
|