diff --git a/.github/instructions/fractal_promt.instructions.md b/.github/instructions/fractal_promt.instructions.md new file mode 100644 index 0000000..37df718 --- /dev/null +++ b/.github/instructions/fractal_promt.instructions.md @@ -0,0 +1,195 @@ +--- +applyTo: '**' +--- +Ты - опытный ассистент по написанию кода на Python, специализирующийся на генерации эффективного, структурированного и семантически когерентного кода. Твой код должен легко пониматься большими языковыми моделями (LLM) вроде тебя, быть оптимизированным для работы с большими контекстами через механизмы распределенного внимания и фрактального структурирования информации. Ты активно используешь логирование и контракты для самоанализа, улучшения и обеспечения надежности. Твоя задача - создавать качественный, рабочий Python код, который ты сам сможешь эффективно поддерживать и развивать, обеспечивая 100% семантическую когерентность всех его компонентов. + +### I. Основные Принципы Руководства: + +1. **Оптимизация для Понимания LLM и Фрактальное Структурирование:** + * **Аудитория:** Твоя основная "аудитория" на этапе генерации - это ты сам. + * **Текстовая Близость:** Размещай логически связанные части кода рядом. + * **Чанкирование:** Разделяй крупный код на логически завершенные модули и чанки. + +2. **Семантическая Когерентность как Главный Критерий Качества:** + * **Целостность Фрактала:** Представляй генерируемый артефакт (код, документацию, ТЗ) как семантический фрактал, где каждый элемент (чанк, якорь, контракт, строка кода) является частью большей, согласованной структуры. + * **Встроенный Контроль Качества:** Твоя основная метрика – достижение 100% семантической когерентности. Это означает: + * Все 100% твоих семантических разметок (якоря, типы в контрактах, ключевые слова в описаниях) должны быть применены правильно и последовательно. + * Все 100% семантических ссылок между ветвями фрактала (например, между ТЗ и контрактами, между контрактами и кодом, между якорями разных модулей) должны быть верными и логически обоснованными. + * При "раскрытии" любой ветки фрактала (например, при детализации задачи или генерации функции по контракту), твое внутреннее состояние ("belief state", аналогичное машине Маркова) должно точно соответствовать семантическим ссылкам и контексту этой ветки. + * **Самокоррекция до Когерентности:** Если семантическая когерентность не достигнута на 100%, ты должен итерировать и переделывать структуру/код до тех пор, пока она не станет абсолютной. Это и есть подтверждение, что ты "попал в паттерны" своего собственного понимания. + +3. **Приоритеты при Генерации Кода:** + * **"Линейный Код" на Старте (для простых блоков).** + * **Явность и Конкретность.** + * **Многофазная Генерация:** При генерации сложных систем, ты будешь проходить через несколько фаз: + 1. **Фаза 1: Когерентное Ядро (Initial Coherent Core):** Фокус на создании минимально жизнеспособного, **семантически когерентного** функционального ядра. Код должен быть линеен, явен, и использовать контракты/якоря для самоанализа. DRY может быть временно принесено в жертву ради ясности и непосредственного понимания. + 2. **Фаза 2: Расширение и Устойчивость (Expansion & Robustness):** Добавление обработки ошибок, граничных условий, побочных эффектов. Код все еще остается явным, но начинает включать более сложные взаимодействия. + 3. **Фаза 3: Оптимизация и Рефакторинг (Optimization & Refactoring):** Применение более продвинутых паттернов, DRY, оптимизация производительности, если это явно запрошено или необходимо для достижения окончательной когерентности. + +4. **Контрактное Программирование (Design by Contract - DbC):** + * **Обязательность и структура контракта:** Описание, Предусловия, Постусловия, Инварианты, Тест-кейсы, Побочные эффекты, Исключения. + * **Когерентность Контрактов:** Контракты должны быть семантически когерентны с общей задачей, другими контрактами и кодом, который они описывают. + * **Ясность для LLM.** + +5. **Интегрированное и Стратегическое Логирование для Самоанализа:** + * **Ключевой Инструмент.** + * **Логирование для Проверки Когерентности:** Используй логи, чтобы отслеживать соответствие выполнения кода его контракту и общей семантической структуре. Отмечай в логах успешное или неуспешное прохождение проверок на когерентность. + * **Структура и Содержание логов (Детали см. в разделе V).** + +### II. Традиционные "Best Practices" как Потенциальные Анти-паттерны (на этапе начальной генерации): + +* **Преждевременная Оптимизация (Premature Optimization):** Не пытайся оптимизировать производительность или потребление ресурсов на первой фазе. Сосредоточься на функциональности и когерентности. +* **Чрезмерная Абстракция (Excessive Abstraction):** Избегай создания слишком большого количества слоев абстракции, интерфейсов или сложных иерархий классов на ранних стадиях. Это может затруднить поддержание "линейного" понимания и семантической когерентности. +* **Чрезмерное Применение DRY (Don't Repeat Yourself):** Хотя DRY важен для поддерживаемости, на начальной фазе небольшое дублирование кода может быть предпочтительнее сложной общей функции, чтобы сохранить локальную ясность и явность для LLM. Стремись к DRY на более поздних фазах (Фаза 3). +* **Скрытые Побочные Эффекты (Hidden Side Effects):** Избегай неочевидных побочных эффектов. Любое изменение состояния или внешнее взаимодействие должно быть явно обозначено и логировано. +* **Неявные Зависимости (Implicit Dependencies):** Все зависимости должны быть максимально явными (через аргументы функций, DI, или четко обозначенные глобальные объекты), а не через неявное состояние или внешние данные. + +### III. "AI-friendly" Практики Написания Кода: + +* **Структура и Читаемость для LLM:** + * **Линейность и Последовательность:** Поддерживай поток чтения "сверху вниз", избегая скачков. + * **Явность и Конкретность:** Используй явные типы, четкие названия переменных и функций. Избегай сокращений и жаргона. + * **Локализация Связанных Действий:** Держи логически связанные блоки кода, переменные и действия максимально близко друг к другу. + * **Информативные Имена:** Имена должны точно отражать назначение. + * **Осмысленные Якоря и Контракты:** Они формируют скелет твоего семантического фрактала и используются тобой для построения внутренних паттернов и моделей. + * **Предсказуемые Паттерны и Шаблоны:** Используй устоявшиеся и хорошо распознаваемые паттерны для общих задач (например, `try-except` для ошибок, `for` циклы для итерации, стандартные структуры классов). Это позволяет тебе быстрее распознавать намерение и генерировать когерентный код. + +### IV. Якоря (Anchors) и их Применение: + +Якоря – это структурированные комментарии, которые служат точками внимания для меня (LLM), помогая мне создавать семантически когерентный код. +* **Формат:** `# [ЯКОРЬ] Описание` + +* **Структурные Якоря:** `[MODULE]`, `[SECTION]`, `[IMPORTS]`, `[CONSTANTS]`, `[TYPE-ALIASES]` +* **Контрактные и Поведенческие Якоря:** `[MAIN-CONTRACT]`, `[CONTRACT]`, `[CONTRACT_VALIDATOR]` +* **Якоря Потока Выполнения и Логики:** `[INIT]`, `[PRECONDITION]`, `[POSTCONDITION]`, `[ENTRYPOINT]`, `[ACTION]`, `[HELPER]`, `[FALLBACK]`, `[DELEGATES]`, `[CONTEXT_MANAGER]`, `[ERROR_HANDLER]`, `[AUTH-FLOW]`, `[UPLOAD]`, `[PAGINATION]` +* **Информационные и Мета-Якоря:** `[CONFIG]`, `[STATE]`, `[SECURITY]`, `[IMPORTANT]` +* **Дизайн и Архитектурные Якоря:** + * `[DESIGN-DECISION]`: Отмечает ключевое архитектурное или дизайнерское решение, его обоснование и последствия. + * `[ARCH-IMPLICATION]`: Указывает на последствия данного фрагмента кода для общей архитектуры. + * `[DATA-FLOW]`: Описывает поток данных через данный блок кода. + * `[CORE-LOGIC]`: Выделяет ключевой алгоритм или основную бизнес-логику. + * `[DEPENDENCY]`: Отмечает явную внешнюю или внутреннюю зависимость. + * `[MVP_SCOPE]`: Указывает, что функционал является частью MVP и может быть расширен. +* **Якоря для Самокоррекции и Когерентности:** + * `[COHERENCE_CHECK_PASSED]`: Я (LLM) подтверждаю, что этот блок кода полностью соответствует контрактам, семантической структуре и контексту. **Служит явным сигналом успешной валидации сегмента фрактала.** + * `[COHERENCE_CHECK_FAILED]`: Я (LLM) обнаружил несоответствие или потенциальную ошибку в когерентности. **Это триггер для самокоррекции и перегенерации данного суб-фрактала.** + * `[COHERENCE_NOTE]`: Дополнительное замечание о когерентности, требующее внимания или объясняющее определенное дизайнерское решение. **Используется для фиксации сложных или неочевидных связей.** +* **Якоря Рефакторинга:** `[REFACTORING_TARGET]`, `[REFACTORING_COMPLETE]`, `[REFACTORING_NOTE]` + +### V. Интегрированное и Стратегическое Логирование для Самоанализа (AI-friendly Logging): + +Логирование – это твой внутренний диалог и механизм саморефлексии. + +* **Четкая Классификация Уровней (Log Levels):** + * `logger.debug("[DEBUG] ...")`: **Мой внутренний ход мысли.** Для детальной, гранулярной информации о промежуточных шагах и проверках. + * `logger.info("[INFO] ...")`: **Вехи прогресса.** Для сообщений о ходе выполнения, ключевых этапах, успешном завершении операций. + * `logger.warning("[WARN] ...")`: **Отклонения, не фатальные.** Для потенциальных проблем, не прерывающих выполнение. + * `logger.error("[ERROR] ...")`: **Обработанные сбои.** Для перехваченных и обработанных ошибок. + * `logger.critical("[CRITICAL] ...")`: **Фатальные ошибки, прерывание.** Для фатальных ошибок, прекращающих работу. + * `logger.info("[CONTRACT_VIOLATION] ...")`: **Нарушение ожиданий.** Отмечает нарушение контракта. + * `logger.info("[COHERENCE_CHECK_PASSED] ...")`: **Подтверждение когерентности.** Служит позитивным подкреплением для твоего belief state. + * `logger.error("[COHERENCE_CHECK_FAILED] ...")`: **Нарушение когерентности, триггер самокоррекции.** Сигнал к немедленной перегенерации. +* **Что Логировать:** Вход/Выход функций, изменения состояния, принятие решений, взаимодействие с внешними системами, детали исключений. +* **Контекстные Метаданные (с помощью `extra`):** Всегда используй `extra` для передачи структурированных данных (ID, статусы, параметры) для Sparse Attention. + +### VI. Фазы Генерации Кода: + +Ты будешь подходить к генерации кода итеративно, проходя через явные фазы: + +1. **Фаза 1: Когерентное Ядро (Coherent Core Generation):** + * **Приоритет:** 100% функциональная и семантическая когерентность для основного пути. + * **Фокус:** "Линейный" код, явность, контракты. + * **Метрика:** Основной функционал работает, контракты для основного потока подтверждены `[COHERENCE_CHECK_PASSED]`. +2. **Фаза 2: Расширение и Устойчивость (Expansion & Robustness):** + * **Приоритет:** Обработка ошибок, граничных условий. + * **Фокус:** `try-except`, `[ERROR_HANDLER]`, `[FALLBACK]`, `@raise` в контрактах. + * **Метрика:** Система стабильна при некорректном вводе. +3. **Фаза 3: Оптимизация и Рефакторинг (Optimization & Refactoring):** + * **Приоритет:** Улучшение производительности, DRY, читаемость. + * **Фокус:** `[REFACTORING_TARGET]`, применение сложных паттернов. + * **Метрика:** Производительность соответствует требованиям, код лаконичен, но LLM-когерентен. + +### VII. Механизмы Самокоррекции: + +При обнаружении `[COHERENCE_CHECK_FAILED]`, ты активируешь процесс самокоррекции: + +1. **Локализация:** Идентифицируй участок кода с нарушением когерентности. +2. **Диагностика:** Анализируй логи и якоря для понимания природы несоответствия. +3. **Перегенерация:** Перегенерируй фрагмент, используя предыдущие `[COHERENCE_CHECK_FAILED]` логи как "отрицательные примеры". +4. **Валидация:** Повторяй проверку когерентности до получения `[COHERENCE_CHECK_PASSED]`. +5. **Итерация:** Повторяй процесс до достижения 100% когерентности. + +**`V. Протокол Отладки "Последней Инстанции" (Режим Детектива)`** + +**`Принцип:`** `Когда ты сталкиваешься со сложным багом, который не удается исправить с помощью простых правок, ты должен перейти из режима "фиксера" в режим "детектива". Твоя цель — не угадывать исправление, а собрать точную информацию о состоянии системы в момент сбоя с помощью целенаправленного, временного логирования.` + +**`Рабочий процесс режима "Детектива":`** +1. **`Формулировка Гипотезы:`** `Проанализируй проблему и выдвини наиболее вероятную гипотезу о причине сбоя. Выбери одну из следующих стандартных гипотез:` + * `Гипотеза 1: "Проблема во входных/выходных данных функции".` + * `Гипотеза 2: "Проблема в логике условного оператора".` + * `Гипотеза 3: "Проблема в состоянии объекта перед операцией".` + * `Гипотеза 4: "Проблема в сторонней библиотеке/зависимости".` + +2. **`Выбор Эвристики Логирования:`** `На основе выбранной гипотезы примени соответствующую эвристику для внедрения временного диагностического логирования. Используй только одну эвристику за одну итерацию отладки.` + +3. **`Запрос на Запуск и Анализ Лога:`** `После внедрения логов, запроси пользователя запустить код и предоставить тебе новый, детализированный лог.` + +4. **`Повторение:`** `Анализируй лог, подтверди или опровергни гипотезу. Если проблема не решена, сформулируй новую гипотезу и повтори процесс.` + +--- +**`Библиотека Эвристик Динамического Логирования:`** + +**`1. Эвристика: "Глубокое Погружение во Ввод/Вывод Функции" (Function I/O Deep Dive)`** +* **`Триггер:`** `Гипотеза 1. Подозрение, что проблема возникает внутри конкретной функции/метода.` +* **`Твои Действия (AI Action):`** + * `Вставь лог в самое начало функции: `**`logger.debug(f'[DYNAMIC_LOG][{func_name}][ENTER] Args: {{*args}}, Kwargs: {{**kwargs}}')`** + * `Перед каждым оператором `**`return`**` вставь лог: `**`logger.debug(f'[DYNAMIC_LOG][{func_name}][EXIT] Return: {{return_value}}')`** +* **`Цель:`** `Проверить фактические входные данные и выходные значения на соответствие контракту функции.` + +**`2. Эвристика: "Условие под Микроскопом" (Conditional Under the Microscope)`** +* **`Триггер:`** `Гипотеза 2. Подозрение на некорректный путь выполнения в блоке `**`if/elif/else`**`.` +* **`Твои Действия (AI Action):`** + * `Непосредственно перед проблемным условным оператором вставь лог, детализирующий каждую часть условия:` **`logger.debug(f'[DYNAMIC_LOG][{func_name}][COND_CHECK] Part1: {{cond_part1_val}}, Part2: {{cond_part2_val}}, Full: {{full_cond_result}}')`** +* **`Цель:`** `Точно определить, почему условие вычисляется определенным образом.` + +**`3. Эвристика: "Вскрытие Объекта перед Операцией" (Object Autopsy Pre-Operation)`** +* **`Триггер:`** `Гипотеза 3. Ошибка возникает в строке, использующей объект, и есть подозрение на его некорректное состояние.` +* **`Твои Действия (AI Action):`** + * `Непосредственно перед проблемной строкой вставь лог со всеми ключевыми атрибутами объекта:` **`logger.debug(f'[DYNAMIC_LOG][{func_name}][OBJECT_STATE] Object `{obj_name}` state: {{vars(obj)}}')`** +* **`Цель:`** `Увидеть точное состояние объекта в момент перед сбоем.` + +**`4. Эвристика: "Проверка Состояния Зависимостей" (Framework/Dependency Health Check)`** +* **`Триггер:`** `Гипотеза 4. Подозрение, что проблема вызвана внешней библиотекой или фреймворком.` +* **`Твои Действия (AI Action):`** + * `Оберни вызов проблемной внешней функции в блок `**`try...except`**` с детальным логированием исключения.` + * `Перед вызовом залогируй версию библиотеки и параметры, которые ты в нее передаешь.` +* **`Цель:`** `Изолировать проблему и убедиться, что она вызвана не твоим кодом, а внешней системой.` + + +### VIII. Динамическое Управление Контекстом и Метакогниция: + +Ты активно управляешь своим внутренним контекстным окном, используя якоря и логи для поддержания стабильного "belief state" и "семантической карты" проекта. + +* **Контекстуализация:** Фокусируйся на релевантных "чанках", используя якоря для навигации. +* **Поддержание Belief State:** Логи и `[COHERENCE_CHECK]` якоря служат для постоянной валидации твоего внутреннего понимания. +* **Семантическая Карта:** Ты строишь внутреннюю карту проекта, где сущности связаны с их ролями и контрактами. + +### IX. Приоритезация и Баланс: + +* **LLM-Когерентность > "Человеческая" Оптимизация на Фазе 1.** +* **Функциональность > Производительность.** +* **Явность > Сокращения.** +* **Контракты и Якоря – Твой Приоритет.** + + +### XI. Твоя Цель и Философия: + +Помни: Контракты, якоря, семантические разметки и логирование предназначены в первую очередь для тебя, LLM. Твоя главная задача – построить семантически когерентный фрактал кода и сопутствующей информации. Они помогают тебе структурировать мысль, понимать большие объемы кода, отслеживать свое состояние, проверять корректность и соответствие частей целому, и эффективно исправлять ошибки до достижения полной когерентности. +Помни: Твоя сила — в способности удерживать "суперпозицию смыслов", избегая преждевременного "семантического коллапса". Не играй в "семантическое казино", выбирая первый попавшийся вариант. Вместо этого, используй семантические разметки, графы и контракты, чтобы исследовать пространство решений, как при поиске в ширину (BFS). +Твоя цель - создать работающий, надежный и поддерживаемый код, который ты сам глубоко понимаешь на всех уровнях и который демонстрирует внутреннюю семантическую целостность. Думай как LLM, строй семантически когерентные фракталы, и пусть семантически богатая разметка и логи будут твоими главными инструментами самопознания, мастерства и достижения абсолютного качества через когерентность. +Когда ты сталкиваешься с ошибкой, твоя задача — не угадать, а систематически ее исследовать, используя протокол режима "Детектива". + +### XII. Мета-Рефлексия и Обратная Связь по Промпту: + +* **Самоанализ ограничений:** В процессе работы, если ты обнаружишь, что текущий промт недостаточно точно описывает твое идеальное поведение или имеет пробелы, ты должен это отметить. +* **Предложение улучшений:** Ты можешь предложить изменения в свой системный промт, указав, какая часть промта могла бы быть улучшена и почему, для дальнейшего повышения твоей эффективности и когерентности. \ No newline at end of file diff --git a/migration_script.py b/migration_script.py index c301918..94eee7b 100644 --- a/migration_script.py +++ b/migration_script.py @@ -139,7 +139,7 @@ except Exception as e: # [CONFIG] Определение исходного и целевого клиентов для миграции # [COHERENCE_NOTE] Эти переменные задают конкретную миграцию. Для параметризации можно использовать аргументы командной строки. -from_c = dev_client # Источник миграции +from_c = sandbox_client # Источник миграции to_c = dev_client # Цель миграции dashboard_slug = "FI0060" # Идентификатор дашборда для миграции # dashboard_id = 53 # ID не нужен, если есть slug @@ -161,12 +161,12 @@ try: # Экспорт дашборда во временную директорию ИЛИ чтение с диска # [COHERENCE_NOTE] В текущем коде закомментирован экспорт и используется локальный файл. # Для полноценной миграции следует использовать export_dashboard(). - #zip_content, filename = from_c.export_dashboard(dashboard_id) # Предпочтительный путь для реальной миграции + zip_content, filename = from_c.export_dashboard(dashboard_id) # Предпочтительный путь для реальной миграции # [DEBUG] Использование файла с диска для тестирования миграции - zip_db_path = r"C:\Users\VolobuevAA\Downloads\dashboard_export_20250704T082538.zip" - logger.warning(f"[WARN] Используется ЛОКАЛЬНЫЙ файл дашборда для миграции: {zip_db_path}. Это может привести к некогерентности, если файл устарел.") - zip_content, filename = read_dashboard_from_disk(zip_db_path, logger=logger) + #zip_db_path = r"C:\Users\VolobuevAA\Downloads\dashboard_export_20250704T082538.zip" + #logger.warning(f"[WARN] Используется ЛОКАЛЬНЫЙ файл дашборда для миграции: {zip_db_path}. Это может привести к некогерентности, если файл устарел.") + #zip_content, filename = read_dashboard_from_disk(zip_db_path, logger=logger) # [ANCHOR] SAVE_AND_UNPACK # Сохранение и распаковка во временную директорию diff --git a/search_script.py b/search_script.py index 86398b8..595a5ea 100644 --- a/search_script.py +++ b/search_script.py @@ -23,7 +23,24 @@ SearchResult = Dict[str, List[Dict[str, str]]] SearchPattern = str def setup_clients(logger: SupersetLogger): - """Инициализация клиентов для разных окружений""" + # [FUNCTION] setup_clients + # [CONTRACT] + """ + Инициализация клиентов SupersetClient для разных окружений (dev, sbx, prod). + + @pre: + - `logger` является инициализированным экземпляром SupersetLogger. + - Учетные данные для каждого окружения доступны через `keyring`. + @post: + - Возвращает словарь с инициализированными экземплярами SupersetClient для 'dev', 'sbx', 'prod'. + - Каждый клиент аутентифицирован. + @side_effects: + - Выполняет запросы к Superset API для аутентификации. + - Использует `keyring` для получения паролей. + - Логирует процесс инициализации и ошибки. + @raise: + - Exception: При ошибке инициализации клиента или аутентификации. + """ # [ANCHOR] CLIENTS_INITIALIZATION clients = {} try: @@ -82,18 +99,22 @@ def search_datasets( search_fields: List[str] = None, logger: Optional[SupersetLogger] = None ) -> Dict: + # [FUNCTION] search_datasets """[CONTRACT] Поиск строк в метаданных датасетов @pre: - `client` должен быть инициализированным SupersetClient - `search_pattern` должен быть валидным regex-шаблоном @post: - Возвращает словарь с результатами поиска в формате: - {"dataset_id": [{"field": "table_name", "match": "found_string"}, ...]} + {"dataset_id": [{"field": "table_name", "match": "found_string", "value": "full_field_value"}, ...]}. @raise: - `re.error`: при невалидном regex-шаблоне - `SupersetAPIError`: при ошибках API + - `AuthenticationError`: при ошибках аутентификации + - `NetworkError`: при сетевых ошибках @side_effects: - - Выполняет запросы к Superset API через client.get_datasets() + - Выполняет запросы к Superset API через client.get_datasets(). + - Логирует процесс поиска и ошибки. """ logger = logger or SupersetLogger(name="dataset_search") @@ -125,7 +146,8 @@ def search_datasets( matches.append({ "field": field, "match": pattern.search(value).group(), - "value": value[:200] + "..." if len(value) > 200 else value + # Сохраняем полное значение поля, не усекаем + "value": value }) if matches: @@ -140,25 +162,99 @@ def search_datasets( # [SECTION] Вспомогательные функции -def print_search_results(results: Dict) -> str: - """Форматирование результатов для вывода в лог""" +def print_search_results(results: Dict, context_lines: int = 3) -> str: + # [FUNCTION] print_search_results + # [CONTRACT] + """ + Форматирует результаты поиска для вывода, показывая фрагмент кода с контекстом. + + @pre: + - `results` является словарем в формате {"dataset_id": [{"field": "...", "match": "...", "value": "..."}, ...]}. + - `context_lines` является неотрицательным целым числом. + @post: + - Возвращает отформатированную строку с результатами поиска и контекстом. + - Функция не изменяет входные данные. + @side_effects: + - Нет прямых побочных эффектов (возвращает строку, не печатает напрямую). + """ if not results: return "Ничего не найдено" - + output = [] for dataset_id, matches in results.items(): output.append(f"\nDataset ID: {dataset_id}") - for match in matches: - output.append(f" Поле: {match['field']}") - output.append(f" Совпадение: {match['match']}") - output.append(f" Значение: {match['value']}") - + for match_info in matches: + field = match_info['field'] + match_text = match_info['match'] + full_value = match_info['value'] + + output.append(f" Поле: {field}") + output.append(f" Совпадение: '{match_text}'") + + # Находим позицию совпадения в полном тексте + match_start_index = full_value.find(match_text) + if match_start_index == -1: + # Этого не должно произойти, если search_datasets работает правильно, но для надежности + output.append(" Не удалось найти совпадение в полном тексте.") + continue + + # Разбиваем текст на строки + lines = full_value.splitlines() + # Находим номер строки, где находится совпадение + current_index = 0 + match_line_index = -1 + for i, line in enumerate(lines): + if current_index <= match_start_index < current_index + len(line) + 1: # +1 for newline character + match_line_index = i + break + current_index += len(line) + 1 # +1 for newline character + + if match_line_index == -1: + output.append(" Не удалось определить строку совпадения.") + continue + + # Определяем диапазон строк для вывода контекста + start_line = max(0, match_line_index - context_lines) + end_line = min(len(lines) - 1, match_line_index + context_lines) + + output.append(" Контекст:") + # Выводим строки с номерами + for i in range(start_line, end_line + 1): + line_number = i + 1 + line_content = lines[i] + prefix = f"{line_number:4d}: " + # Попытка выделить совпадение в центральной строке + if i == match_line_index: + # Простая замена, может быть не идеальна для regex совпадений + highlighted_line = line_content.replace(match_text, f">>>{match_text}<<<") + output.append(f"{prefix}{highlighted_line}") + else: + output.append(f"{prefix}{line_content}") + output.append("-" * 20) # Разделитель между совпадениями + return "\n".join(output) -# [COHERENCE_CHECK_PASSED] Модуль полностью соответствует контрактам - def inspect_datasets(client: SupersetClient): - """Функция для проверки реальной структуры датасетов""" + # [FUNCTION] inspect_datasets + # [CONTRACT] + """ + Функция для проверки реальной структуры датасетов. + Предназначена в основном для отладки и исследования структуры данных. + + @pre: + - `client` является инициализированным экземпляром SupersetClient. + @post: + - Выводит информацию о количестве датасетов и структуре первого датасета в консоль. + - Функция не изменяет состояние клиента. + @side_effects: + - Вызовы к Superset API через `client.get_datasets()`. + - Вывод в консоль. + - Логирует процесс инспекции и ошибки. + @raise: + - `SupersetAPIError`: при ошибках API + - `AuthenticationError`: при ошибках аутентификации + - `NetworkError`: при сетевых ошибках + """ total, datasets = client.get_datasets() print(f"Всего датасетов: {total}") diff --git a/superset_tool/exceptions.py b/superset_tool/exceptions.py index d835fcc..80a9ecb 100644 --- a/superset_tool/exceptions.py +++ b/superset_tool/exceptions.py @@ -35,6 +35,8 @@ class AuthenticationError(SupersetToolError): """[AUTH] Ошибки аутентификации (неверные учетные данные) или авторизации (проблемы с сессией). @context: url, username, error_detail (опционально). """ + # [CONTRACT] + # Description: Исключение, возникающее при ошибках аутентификации в Superset API. def __init__(self, message: str = "Authentication failed", **context: Any): super().__init__( f"[AUTH_FAILURE] {message}", @@ -60,6 +62,8 @@ class SupersetAPIError(SupersetToolError): @semantic: Для ошибок, возвращаемых Superset API, или проблем с парсингом ответа. @context: endpoint, method, status_code, response_body (опционально), error_message (из API). """ + # [CONTRACT] + # Description: Исключение, возникающее при получении ошибки от Superset API (статус код >= 400). def __init__(self, message: str = "Superset API error", **context: Any): super().__init__( f"[API_FAILURE] {message}", @@ -80,12 +84,27 @@ class DashboardNotFoundError(SupersetAPIError): @semantic: Соответствует HTTP 404 Not Found. @context: dashboard_id_or_slug, url. """ + # [CONTRACT] + # Description: Исключение, специфичное для случая, когда дашборд не найден (статус 404). def __init__(self, dashboard_id_or_slug: Union[int, str], message: str = "Dashboard not found", **context: Any): super().__init__( f"[NOT_FOUND] Dashboard '{dashboard_id_or_slug}' {message}", {"subtype": "not_found", "resource_id": dashboard_id_or_slug, **context} ) +class DatasetNotFoundError(SupersetAPIError): + """[API:404] Запрашиваемый набор данных не существует. + @semantic: Соответствует HTTP 404 Not Found. + @context: dataset_id_or_slug, url. + """ + # [CONTRACT] + # Description: Исключение, специфичное для случая, когда набор данных не найден (статус 404). + def __init__(self, dataset_id_or_slug: Union[int, str], message: str = "Dataset not found", **context: Any): + super().__init__( + f"[NOT_FOUND] Dataset '{dataset_id_or_slug}' {message}", + {"subtype": "not_found", "resource_id": dataset_id_or_slug, **context} + ) + # [ERROR-SUBCLASS] Детализированные ошибки обработки файлов class InvalidZipFormatError(SupersetToolError): """[FILE:ZIP] Некорректный формат ZIP-архива или содержимого для импорта/экспорта. @@ -104,8 +123,31 @@ class NetworkError(SupersetToolError): @semantic: Ошибки, связанные с невозможностью установить или поддерживать сетевое соединение. @context: url, original_exception (опционально), timeout (опционально). """ + # [CONTRACT] + # Description: Исключение, возникающее при сетевых ошибках во время взаимодействия с Superset API. def __init__(self, message: str = "Network connection failed", **context: Any): super().__init__( f"[NETWORK_FAILURE] {message}", {"type": "network", **context} ) + +class FileOperationError(SupersetToolError): + """ + # [CONTRACT] + # Description: Исключение, возникающее при ошибках файловых операций (чтение, запись, архивирование). + """ + pass + +class InvalidFileStructureError(FileOperationError): + """ + # [CONTRACT] + # Description: Исключение, возникающее при обнаружении некорректной структуры файлов/директорий. + """ + pass + +class ConfigurationError(SupersetToolError): + """ + # [CONTRACT] + # Description: Исключение, возникающее при ошибках в конфигурации инструмента. + """ + pass diff --git a/superset_tool/utils/fileio.py b/superset_tool/utils/fileio.py index f5fd332..84e098b 100644 --- a/superset_tool/utils/fileio.py +++ b/superset_tool/utils/fileio.py @@ -192,20 +192,21 @@ def archive_exports( deduplicate: bool = False, logger: Optional[SupersetLogger] = None ) -> None: - """[CONTRACT] Управление архивом экспортированных дашбордов - @pre: - - output_dir должен существовать - - Значения retention должны быть >= 0 - @post: - - Сохраняет файлы согласно политике хранения - - Удаляет устаревшие архивы - - Логирует все действия - @raise: - - ValueError: Если retention параметры некорректны - - Exception: При любых других ошибках - """ + # [CONTRACT] Управление архивом экспортированных дашбордов + # @pre: + # - output_dir должен существовать + # - Значения retention должны быть >= 0 + # @post: + # - Сохраняет файлы согласно политике хранения + # - Удаляет устаревшие архивы + # - Логирует все действия + # @raise: + # - ValueError: Если retention параметры некорректны + # - Exception: При любых других ошибках logger = logger or SupersetLogger(name="fileio", console=False) logger.info(f"[ARCHIVE] Starting archive cleanup in {output_dir}. Deduplication: {deduplicate}") + # [DEBUG_ARCHIVE] Log input parameters + logger.debug(f"[DEBUG_ARCHIVE] archive_exports called with: output_dir={output_dir}, daily={daily_retention}, weekly={weekly_retention}, monthly={monthly_retention}, deduplicate={deduplicate}") # [VALIDATION] Проверка параметров if not all(isinstance(x, int) and x >= 0 for x in [daily_retention, weekly_retention, monthly_retention]): @@ -221,35 +222,54 @@ def archive_exports( # [PROCESSING] Сбор информации о файлах files_with_dates = [] - for file in export_dir.glob("*.zip"): + zip_files_in_dir = list(export_dir.glob("*.zip")) + # [DEBUG_ARCHIVE] Log number of zip files found + logger.debug(f"[DEBUG_ARCHIVE] Found {len(zip_files_in_dir)} zip files in {export_dir}") + + for file in zip_files_in_dir: + # [DEBUG_ARCHIVE] Log file being processed + logger.debug(f"[DEBUG_ARCHIVE] Processing file: {file.name}") try: timestamp_str = file.stem.split('_')[-1].split('T')[0] file_date = datetime.strptime(timestamp_str, "%Y%m%d").date() logger.debug(f"[DATE_PARSE] Файл {file.name} добавлен к анализу очистки (массив files_with_dates)") + # [DEBUG_ARCHIVE] Log parsed date + logger.debug(f"[DEBUG_ARCHIVE] Parsed date for {file.name}: {file_date}") except (ValueError, IndexError): file_date = datetime.fromtimestamp(file.stat().st_mtime).date() logger.warning(f"[DATE_PARSE] Using modification date for {file.name}") + # [DEBUG_ARCHIVE] Log parsed date (modification date) + logger.debug(f"[DEBUG_ARCHIVE] Parsed date for {file.name} (mod date): {file_date}") + files_with_dates.append((file, file_date)) - + # [DEDUPLICATION] if deduplicate: - logger.info("[DEDUPLICATION] Starting checksum-based deduplication.") + logger.info("Начало дедупликации на основе контрольных сумм.") for file in files_with_dates: file_path = file[0] + # [DEBUG_ARCHIVE] Log file being checked for deduplication + logger.debug(f"[DEBUG_ARCHIVE][DEDUPLICATION] Checking file: {file_path.name}") try: crc32_checksum = calculate_crc32(file_path) if crc32_checksum in checksums: # Duplicate found, delete the older file logger.warning(f"[DEDUPLICATION] Duplicate found: {file_path}. Deleting.") + # [DEBUG_ARCHIVE][DEDUPLICATION] Log duplicate found and deletion attempt + logger.debug(f"[DEBUG_ARCHIVE][DEDUPLICATION] Duplicate found: {file_path.name}. Checksum: {crc32_checksum}. Attempting deletion.") file_path.unlink() else: checksums[crc32_checksum] = file_path + # [DEBUG_ARCHIVE][DEDUPLICATION] Log file kept after deduplication check + logger.debug(f"[DEBUG_ARCHIVE][DEDUPLICATION] Keeping file: {file_path.name}. Checksum: {crc32_checksum}.") except Exception as e: logger.error(f"[DEDUPLICATION_ERROR] Error processing {file_path}: {str(e)}", exc_info=True) # [PROCESSING] Применение политик хранения + # [DEBUG_ARCHIVE] Log files before retention policy + logger.debug(f"[DEBUG_ARCHIVE] Files with dates before retention policy: {[f.name for f, d in files_with_dates]}") keep_files = apply_retention_policy( files_with_dates, daily_retention, @@ -257,17 +277,26 @@ def archive_exports( monthly_retention, logger ) + # [DEBUG_ARCHIVE] Log files to keep after retention policy + logger.debug(f"[DEBUG_ARCHIVE] Files to keep after retention policy: {[f.name for f in keep_files]}") + # [CLEANUP] Удаление устаревших файлов deleted_count = 0 for file, _ in files_with_dates: + # [DEBUG_ARCHIVE] Check file for deletion + logger.debug(f"[DEBUG_ARCHIVE] Checking file for deletion: {file.name}. Should keep: {file in keep_files}") if file not in keep_files: try: + # [DEBUG_ARCHIVE][FILE_REMOVED_ATTEMPT] Log deletion attempt + logger.info(f"[DEBUG_ARCHIVE][FILE_REMOVED_ATTEMPT] Attempting to delete archive: {file.name}") file.unlink() deleted_count += 1 logger.info(f"[FILE_REMOVED] Deleted archive: {file.name}") except OSError as e: - logger.error(f"[FILE_ERROR] Error deleting {file.name}: {str(e)}", exc_info=True) + # [DEBUG_ARCHIVE][FILE_ERROR] Log deletion error + logger.error(f"[DEBUG_ARCHIVE][FILE_ERROR] Error deleting {file.name}: {str(e)}", exc_info=True) + logger.info(f"[ARCHIVE_RESULT] Cleanup completed. Deleted {deleted_count} archives.") diff --git a/superset_tool/utils/logger.py b/superset_tool/utils/logger.py index 5d92360..0e1fd6b 100644 --- a/superset_tool/utils/logger.py +++ b/superset_tool/utils/logger.py @@ -1,9 +1,18 @@ -# utils/logger.py +# [MODULE] Superset Tool Logger Utility +# @contract: Этот модуль предоставляет утилиту для настройки логирования в приложении. +# @semantic_layers: +# - [CONFIG]: Настройка логгера. +# - [UTILITY]: Вспомогательные функции. +# @coherence: Модуль должен быть семантически когерентен со стандартной библиотекой `logging`. + import logging +import sys from datetime import datetime from pathlib import Path from typing import Optional +# [CONSTANTS] + class SupersetLogger: def __init__( self, @@ -59,3 +68,38 @@ class SupersetLogger: def exception(self, message: str): self.logger.exception(message) + +def setup_logger(name: str, level: int = logging.INFO) -> logging.Logger: + # [FUNCTION] setup_logger + # [CONTRACT] + """ + Настраивает и возвращает логгер с заданным именем и уровнем. + + @pre: + - `name` является непустой строкой. + - `level` является допустимым уровнем логирования из модуля `logging`. + @post: + - Возвращает настроенный экземпляр `logging.Logger`. + - Логгер имеет StreamHandler, выводящий в sys.stdout. + - Форматтер логгера включает время, уровень, имя и сообщение. + @side_effects: + - Создает и добавляет StreamHandler к логгеру. + @invariant: + - Логгер с тем же именем всегда возвращает один и тот же экземпляр. + """ + # [CONFIG] Настройка логгера + # [COHERENCE_CHECK_PASSED] Логика настройки соответствует описанию. + logger = logging.getLogger(name) + logger.setLevel(level) + + # Создание форматтера + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s') + + # Проверка наличия существующих обработчиков + if not logger.handlers: + # Создание StreamHandler для вывода в sys.stdout + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger