our_knp/examples/ci/run_pylint.py
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

155 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""
@file run_pylint.py
@brief PyLint CI runner.
@kaspersky_support Artiom N.
@license Apache 2.0 License.
@copyright © 2024 AO Kaspersky Lab
@date 28.10.2024.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import io
import os
import re
import sys
from pathlib import Path
from typing import Dict, List, NoReturn, Tuple, Union
from pylint.lint.run import Run as PylintRun
from pylint.reporters.text import ParseableTextReporter
class TeeIO(io.StringIO):
"""Combined output: console and file."""
def __init__(self, filepath: Path):
super().__init__()
self._filepath = filepath
self._file = None
def __enter__(self) -> 'TeeIO':
self._file = open(self._filepath, 'w', encoding='utf8') # type: ignore
return self
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
if self._file is not None:
self._file.close()
def write(self, text: str) -> None:
print(text, end='')
if self._file is None:
raise OSError('File must be opened.')
self._file.write(text)
class ExitSuppressor:
real_exit = sys.exit
@staticmethod
def exit_function(code: Union[str, int, None] = 0) -> NoReturn:
pass
def get_pylint_suppressors(
starting_directory: Path, current_directory: Path | None = None, lines: Dict[Path, List[Tuple[int, str]]] = None
) -> Dict[Path, List[Tuple[int, str]]]:
"""Print pylint suppressions."""
if lines is None:
lines = {}
if current_directory is None:
current_directory = starting_directory
for gf in current_directory.rglob('*'):
if gf.is_dir():
get_pylint_suppressors(starting_directory, gf, lines)
elif gf.is_file() and gf.name.endswith('.py'):
with open(gf, encoding='utf8') as pf:
for n, ln in enumerate(pf.readlines()):
# This is not totally correct.
if 'pylint:' not in ln:
continue
lines_list = lines.setdefault(gf.relative_to(starting_directory), [])
lines_list.append((n, ln))
return lines
if len(sys.argv) < 3:
print(f'{Path(__file__).name} <working_directory> <out_directory>')
sys.exit(1)
SCRIPT_DIR = Path(__file__).parent.absolute()
PROJECT_DIR = WORKING_DIR = Path(sys.argv[1])
RC_FILE = PROJECT_DIR / '.pylintrc'
if not RC_FILE.exists():
RC_FILE = PROJECT_DIR.parent / '.pylintrc'
PYLINT_DIR = 'pylint_report'
PYLINT_PATH = Path(sys.argv[2]) / PYLINT_DIR
PYLINT_REPORT_FILE = PYLINT_PATH / 'pylint-report.txt'
WORKING_DIR.cwd()
PYLINT_PATH.mkdir(exist_ok=True, parents=True)
sys.exit = ExitSuppressor.exit_function
os.environ['PYTHON_PATH'] = f'{WORKING_DIR}:{os.environ.get("PYTHON_PATH", "")}'
argv = sys.argv[3:] + [f'--rcfile={RC_FILE}', '--exit-zero', str(WORKING_DIR)]
suppressors = get_pylint_suppressors(WORKING_DIR)
if suppressors:
print(f'Pylint suppressed\n{"=" * 17}\n')
with TeeIO(PYLINT_PATH / 'pylint-disabled.txt') as tf:
for fn, skip_list in suppressors.items():
tf.write(f'{str(fn)}:\n')
for line_no, line in skip_list:
tf.write(f' {line_no}: {line}')
print('-' * 70)
try:
with TeeIO(PYLINT_REPORT_FILE) as rf:
PylintRun(argv, reporter=ParseableTextReporter(output=rf))
except KeyboardInterrupt:
sys.exit(1)
expr_regex = re.compile(r'\[[FEW,]\d+\(')
print('Analyzing report file...')
with open(PYLINT_REPORT_FILE, encoding='utf8') as f:
critical_lines = []
# Create critical report.
while line := f.readline():
if expr_regex.search(line):
critical_lines.append(line.strip())
print(f'Error count = {len(critical_lines)}')
with TeeIO(PYLINT_PATH / 'pylint-report-critical-only.txt') as crf:
crf.write('\n'.join(critical_lines))
# Errors and warnings make the build only fail.
if critical_lines:
print('Pylint found Errors and Warnings. Fix or explain, then rerun.')
if os.environ.get('BYPASS_BLOCKING_PYLINT') is None:
ExitSuppressor.real_exit(1)
else:
print('WARNING: Pylint blocking mechanism was disabled.')
else:
print('No Errors of Warnings were found by Pylint.')