/
Watashicuvu
/
agentic-tools
Обзор
Документация
Войти
/
Watashicuvu
/
agentic-tools
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/services/docstring_filter.py
416 строк
16 KB
Якуб
mts update
12 апр 2026, 21:19
12 апр 2026, 21:19
bff6dcd
Код
Авторство
О чём код?
""" Docstring Filtering Helpers. Модуль для фильтрации файлов и функций при генерации чек-листа докстрингов. Использует эвристики для исключения тестов, шаблонов, простых функций и дандер методов. Example: from src.services.docstring_filter import DocstringFilter filter = DocstringFilter(heuristics) if filter.should_exclude_file(rel_path): continue if filter.should_exclude_function(func_name): continue if filter.is_function_too_simple(func_analysis): continue if filter.needs_docstring(func_analysis): # Add to checklist """ import re from typing import Any, Dict, Optional class DocstringFilter: """ Фильтрация файлов и функций для генерации чек-листа докстрингов. Использует конфигурацию из heuristics.json для определения: - Какие файлы исключить (тесты, шаблоны, сгенерированный код) - Какие функции исключить (простые дандеры, геттеры/сеттеры) - Пороги сложности для документирования - Приоритеты архитектурных зон """ def __init__(self, heuristics: Dict[str, Any]): """ Args: heuristics: Конфигурация эвристик из .codecontext/heuristics.json """ self.heuristics = heuristics self.filter_cfg = heuristics.get("docstring_filtering", {}) self.quality_cfg = heuristics.get("docstring_quality", {}) # Извлекаем конфигурации self._exclude_file_patterns = self.filter_cfg.get("exclude_file_patterns", []) self._exclude_func_patterns = self.filter_cfg.get("exclude_function_patterns", []) self._dunder_cfg = self.filter_cfg.get("include_dunder_if", {}) self._complexity_cfg = self.filter_cfg.get("complexity_threshold", {}) self._zone_priority = self.filter_cfg.get("zone_priority", {}) # Компилируем regex паттерны для производительности self._file_regexes = [re.compile(p) for p in self._exclude_file_patterns] self._func_regexes = [re.compile(p) for p in self._exclude_func_patterns] def should_exclude_file(self, rel_path: str) -> bool: """ Проверяет, нужно ли исключить файл из анализа. Args: rel_path: Относительный путь к файлу (напр. "src/tests/test_file.py") Returns: True если файл нужно исключить Examples: >>> filter = DocstringFilter(heuristics) >>> filter.should_exclude_file("src/tests/test_file.py") True >>> filter.should_exclude_file("src/services/core.py") False """ for regex in self._file_regexes: if regex.match(rel_path): return True return False def should_exclude_function(self, func_name: str) -> bool: """ Проверяет, нужно ли исключить функцию из чек-листа. Args: func_name: Имя функции (напр. "__init__", "validate_payment") Returns: True если функцию нужно исключить Examples: >>> filter = DocstringFilter(heuristics) >>> filter.should_exclude_function("__init__") True >>> filter.should_exclude_function("validate_payment") False """ for regex in self._func_regexes: if regex.match(func_name): return True return False def should_include_dunder(self, func_name: str, func_analysis: Dict[str, Any]) -> bool: """ Определяет, нужно ли включать дандер метод в чек-лист. Дандер методы включаются если: - Имеют сложную логику (≥ min_lines) - Имеют параметры (кроме self) Args: func_name: Имя функции func_analysis: Анализ функции из RepositoryContext Returns: True если дандер метод нужно включить """ # Не дандер — всегда включаем if not (func_name.startswith("__") and func_name.endswith("__")): return True # Получаем метрики min_lines = self._dunder_cfg.get("min_lines", 5) has_params = self._dunder_cfg.get("has_parameters", True) metrics = func_analysis.get("context", {}).get("complexity_metrics", {}) line_count = metrics.get("line_count", 0) param_count = metrics.get("param_count", 0) # Включаем если достаточно строк (сложная логика) if line_count >= min_lines: return True # Включаем если есть параметры (кроме self) if has_params and param_count > 1: return True return False def is_function_too_simple(self, func_analysis: Dict[str, Any]) -> bool: """ Проверяет, является ли функция слишком простой для документирования. Функция считается слишком простой если: - Меньше минимального количества строк - Недостаточно параметров - Нет аннотации возврата (если требуется) - Нет вложенных вызовов (если требуется) Args: func_analysis: Анализ функции из RepositoryContext Returns: True если функция слишком простая """ metrics = func_analysis.get("context", {}).get("complexity_metrics", {}) line_count = metrics.get("line_count", 0) param_count = metrics.get("param_count", 0) has_return = bool(func_analysis.get("context", {}).get("type_semantics", {}).get("return")) nested_calls = metrics.get("nested_call_count", 0) # Проверяем пороговые значения min_lines = self._complexity_cfg.get("min_lines", 3) min_params = self._complexity_cfg.get("min_param_count", 1) require_return = self._complexity_cfg.get("has_return_annotation", False) require_nested = self._complexity_cfg.get("has_nested_calls", False) # Меньше минимального количества строк if line_count < min_lines: return True # Недостаточно параметров if min_params > 0 and param_count < min_params: return True # Нет аннотации возврата (если требуется) if require_return and not has_return: return True # Нет вложенных вызовов (если требуется) if require_nested and nested_calls == 0: return True return False def needs_docstring(self, func_analysis: Dict[str, Any]) -> bool: """ Определяет, требуется ли докстринг для функции. Докстринг требуется если: - Нет докстринги вообще - Докстринга слишком короткая (< min_length) - Много параметров без раздела Args - Есть аннотация возврата без раздела Returns Args: func_analysis: Анализ функции из RepositoryContext Returns: True если докстринг требуется """ docstring = func_analysis.get("context", {}).get("docstring_stub") or "" has_doc = bool(docstring.strip()) # 1. Нет докстринги вообще if not has_doc: return True # 2. Слишком короткая min_length = self.quality_cfg.get("min_length", 50) if len(docstring) < min_length: return True # 3. Много параметров без раздела Args param_count = func_analysis.get("context", {}).get("complexity_metrics", {}).get("param_count", 0) param_threshold = self.quality_cfg.get("param_threshold", 3) if param_count > param_threshold: args_keywords = self.quality_cfg.get("required_sections", {}).get("args_keywords", ["Args:"]) if not any(kw in docstring for kw in args_keywords): return True # 4. Есть аннотация возврата без раздела Returns if func_analysis.get("context", {}).get("type_semantics", {}).get("return"): returns_keywords = self.quality_cfg.get("required_sections", {}).get("returns_keywords", ["Returns:"]) if not any(kw in docstring for kw in returns_keywords): return True return False def get_zone_priority(self, zone: str) -> int: """ Возвращает приоритет для архитектурной зоны. Args: zone: Название зоны (напр. "business_service", "infrastructure") Returns: Приоритет (1 = highest, 6 = lowest) """ return self._zone_priority.get(zone, 6) def get_impact_priority_boost( self, impact_level: str, reverse_deps_count: int = 0 ) -> int: """ Возвращает приоритетный буст на основе влияния функции. Args: impact_level: Уровень влияния ('critical', 'high', 'medium', 'low') reverse_deps_count: Количество обратных зависимостей Returns: Приоритетный буст (отрицательное число для повышения приоритета) """ impact_cfg = self.heuristics.get("docstring_impact_analysis", {}) priority_boost_cfg = impact_cfg.get("priority_boost", { "critical": -2, "high": -1, "medium": 0, "low": 0 }) return priority_boost_cfg.get(impact_level, 0) def calculate_final_priority( self, zone: str, impact_level: str = "low", reverse_deps_count: int = 0 ) -> int: """ Вычисляет итоговый приоритет с учётом зоны и влияния. Args: zone: Архитектурная зона impact_level: Уровень влияния ('critical', 'high', 'medium', 'low') reverse_deps_count: Количество обратных зависимостей Returns: Итоговый приоритет (1 = highest, 6 = lowest) """ base_priority = self.get_zone_priority(zone) impact_boost = self.get_impact_priority_boost(impact_level, reverse_deps_count) return max(1, base_priority + impact_boost) def should_include_function( self, func_name: str, func_analysis: Dict[str, Any], file_zone: Optional[str] = None ) -> bool: """ Комплексная проверка: нужно ли включать функцию в чек-лист. Объединяет все проверки: 1. Исключение по имени функции 2. Проверка дандер методов 3. Проверка сложности 4. Проверка необходимости докстринги Args: func_name: Имя функции func_analysis: Анализ функции из RepositoryContext file_zone: Архитектурная зона файла (опционально) Returns: True если функцию нужно включить в чек-лист """ # 1. Исключаем по имени if self.should_exclude_function(func_name): return False # 2. Проверяем дандер методы if not self.should_include_dunder(func_name, func_analysis): return False # 3. Проверяем сложность if self.is_function_too_simple(func_analysis): return False # 4. Проверяем необходимость докстринги return self.needs_docstring(func_analysis) def get_filter_stats(self) -> Dict[str, Any]: """ Возвращает статистику конфигурации фильтрации. Returns: Статистика с количеством паттернов и настройками """ return { "exclude_file_patterns": len(self._exclude_file_patterns), "exclude_func_patterns": len(self._exclude_func_patterns), "dunder_min_lines": self._dunder_cfg.get("min_lines", 5), "complexity_min_lines": self._complexity_cfg.get("min_lines", 3), "zone_priority_count": len(self._zone_priority) } # ───────────────────────────────────────────────────────────────────────────── # Module-level convenience functions # ───────────────────────────────────────────────────────────────────────────── def create_filter(heuristics: Dict[str, Any]) -> DocstringFilter: """ Создаёт фильтр докстрингов из конфигурации эвристик. Args: heuristics: Конфигурация из .codecontext/heuristics.json Returns: Настроенный экземпляр DocstringFilter """ return DocstringFilter(heuristics) def should_exclude_file(heuristics: Dict[str, Any], rel_path: str) -> bool: """ Проверяет, нужно ли исключить файл из анализа. Convenience function для быстрого использования без создания класса. Args: heuristics: Конфигурация эвристик rel_path: Относительный путь к файлу Returns: True если файл нужно исключить """ filter = DocstringFilter(heuristics) return filter.should_exclude_file(rel_path) def should_exclude_function(heuristics: Dict[str, Any], func_name: str) -> bool: """ Проверяет, нужно ли исключить функцию из чек-листа. Convenience function для быстрого использования. Args: heuristics: Конфигурация эвристик func_name: Имя функции Returns: True если функцию нужно исключить """ filter = DocstringFilter(heuristics) return filter.should_exclude_function(func_name) def needs_docstring(heuristics: Dict[str, Any], func_analysis: Dict[str, Any]) -> bool: """ Определяет, требуется ли докстринг для функции. Convenience function для быстрого использования. Args: heuristics: Конфигурация эвристик func_analysis: Анализ функции из RepositoryContext Returns: True если докстринг требуется """ filter = DocstringFilter(heuristics) return filter.needs_docstring(func_analysis) __all__ = [ "DocstringFilter", "create_filter", "should_exclude_file", "should_exclude_function", "needs_docstring", ]