fix url check

This commit is contained in:
Volobuev Andrey
2025-08-26 17:39:11 +03:00
parent 0e2fc14732
commit 2f8aea3620
11 changed files with 28331 additions and 374 deletions

View File

@@ -1,195 +0,0 @@
---
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. Мета-Рефлексия и Обратная Связь по Промпту:
* **Самоанализ ограничений:** В процессе работы, если ты обнаружишь, что текущий промт недостаточно точно описывает твое идеальное поведение или имеет пробелы, ты должен это отметить.
* **Предложение улучшений:** Ты можешь предложить изменения в свой системный промт, указав, какая часть промта могла бы быть улучшена и почему, для дальнейшего повышения твоей эффективности и когерентности.

5
.gitignore vendored
View File

@@ -2,5 +2,6 @@
*.ps1 *.ps1
keyring passwords.py keyring passwords.py
*logs* *logs*
*\.github* *github*
*venv*
*git*

View File

@@ -27,7 +27,7 @@
</ФИЛОСОФИЯ> </ФИЛОСОФИЯ>
<КАРТАРОЕКТА> <КАРТАРОЕКТА>
<ИМЯ_ФАЙЛА>PROJECT_SEMANTICS.xml</ИМЯ_ФАЙЛА> <ИМЯ_ФАЙЛА>tech_spec/PROJECT_SEMANTICS.xml</ИМЯ_ФАЙЛА>
<НАЗНАЧЕНИЕ> <НАЗНАЧЕНИЕ>
Этот файл является единым источником истины (Single Source of Truth) о семантической структуре всего проекта. Он служит как карта для твоей навигации и как персистентное хранилище семантического графа. Ты обязан загружать его в начале каждой сессии и обновлять в конце. Этот файл является единым источником истины (Single Source of Truth) о семантической структуре всего проекта. Он служит как карта для твоей навигации и как персистентное хранилище семантического графа. Ты обязан загружать его в начале каждой сессии и обновлять в конце.
</НАЗНАЧЕНИЕ> </НАЗНАЧЕНИЕ>

View File

@@ -22,7 +22,8 @@ from superset_tool.utils.fileio import (
archive_exports, archive_exports,
sanitize_filename, sanitize_filename,
consolidate_archive_folders, consolidate_archive_folders,
remove_empty_directories remove_empty_directories,
RetentionPolicy
) )
from superset_tool.utils.init_clients import setup_clients from superset_tool.utils.init_clients import setup_clients
@@ -36,6 +37,7 @@ class BackupConfig:
consolidate: bool = True consolidate: bool = True
rotate_archive: bool = True rotate_archive: bool = True
clean_folders: bool = True clean_folders: bool = True
retention_policy: RetentionPolicy = RetentionPolicy()
# [ENTITY: Function('backup_dashboards')] # [ENTITY: Function('backup_dashboards')]
# CONTRACT: # CONTRACT:
@@ -84,7 +86,7 @@ def backup_dashboards(
) )
if config.rotate_archive: if config.rotate_archive:
archive_exports(str(dashboard_dir), logger=logger) archive_exports(str(dashboard_dir), policy=config.retention_policy, logger=logger)
success_count += 1 success_count += 1
except (SupersetAPIError, RequestException, IOError, OSError) as db_error: except (SupersetAPIError, RequestException, IOError, OSError) as db_error:

View File

@@ -10,9 +10,11 @@
@description: Интерактивный скрипт для миграции ассетов Superset между различными окружениями. @description: Интерактивный скрипт для миграции ассетов Superset между различными окружениями.
""" """
from whiptail import Whiptail
# [IMPORTS] # [IMPORTS]
from superset_tool.client import SupersetClient from superset_tool.client import SupersetClient
from superset_tool.utils.init_clients import init_superset_clients from superset_tool.utils.init_clients import setup_clients
from superset_tool.utils.logger import SupersetLogger from superset_tool.utils.logger import SupersetLogger
from superset_tool.utils.fileio import ( from superset_tool.utils.fileio import (
save_and_unpack_dashboard, save_and_unpack_dashboard,
@@ -69,48 +71,34 @@ class Migration:
"""Шаг 1: Выбор окружений (источник и назначение).""" """Шаг 1: Выбор окружений (источник и назначение)."""
self.logger.info("[INFO][select_environments][ENTER] Шаг 1/4: Выбор окружений.") self.logger.info("[INFO][select_environments][ENTER] Шаг 1/4: Выбор окружений.")
available_envs = {"1": "DEV", "2": "PROD"}
print("Доступные окружения:")
for key, value in available_envs.items():
print(f" {key}. {value}")
while self.from_c is None:
try: try:
from_env_choice = input("Выберите исходное окружение (номер): ") all_clients = setup_clients(self.logger)
from_env_name = available_envs.get(from_env_choice) available_envs = list(all_clients.keys())
if not from_env_name: except Exception as e:
print("Неверный выбор. Попробуйте снова.") self.logger.error(f"[ERROR][select_environments][FAILURE] Ошибка при инициализации клиентов: {e}", exc_info=True)
continue w = Whiptail(title="Ошибка", backtitle="Superset Migration Tool")
w.msgbox("Не удалось инициализировать клиенты. Проверьте конфигурацию.")
return
clients = init_superset_clients(self.logger, env=from_env_name.lower()) w = Whiptail(title="Выбор окружения", backtitle="Superset Migration Tool")
self.from_c = clients[0]
# Select source environment
(return_code, from_env_name) = w.menu("Выберите исходное окружение:", available_envs)
if return_code == 0:
self.from_c = all_clients[from_env_name]
self.logger.info(f"[INFO][select_environments][STATE] Исходное окружение: {from_env_name}") self.logger.info(f"[INFO][select_environments][STATE] Исходное окружение: {from_env_name}")
else:
return
except Exception as e: # Select target environment
self.logger.error(f"[ERROR][select_environments][FAILURE] Ошибка при инициализации клиента-источника: {e}", exc_info=True) available_envs.remove(from_env_name)
print("Не удалось инициализировать клиент. Проверьте конфигурацию.") (return_code, to_env_name) = w.menu("Выберите целевое окружение:", available_envs)
if return_code == 0:
while self.to_c is None: self.to_c = all_clients[to_env_name]
try:
to_env_choice = input("Выберите целевое окружение (номер): ")
to_env_name = available_envs.get(to_env_choice)
if not to_env_name:
print("Неверный выбор. Попробуйте снова.")
continue
if to_env_name == self.from_c.env:
print("Целевое и исходное окружения не могут совпадать.")
continue
clients = init_superset_clients(self.logger, env=to_env_name.lower())
self.to_c = clients[0]
self.logger.info(f"[INFO][select_environments][STATE] Целевое окружение: {to_env_name}") self.logger.info(f"[INFO][select_environments][STATE] Целевое окружение: {to_env_name}")
else:
return
except Exception as e:
self.logger.error(f"[ERROR][select_environments][FAILURE] Ошибка при инициализации целевого клиента: {e}", exc_info=True)
print("Не удалось инициализировать клиент. Проверьте конфигурацию.")
self.logger.info("[INFO][select_environments][EXIT] Шаг 1 завершен.") self.logger.info("[INFO][select_environments][EXIT] Шаг 1 завершен.")
# END_FUNCTION_select_environments # END_FUNCTION_select_environments
@@ -125,51 +113,27 @@ class Migration:
self.logger.info("[INFO][select_dashboards][ENTER] Шаг 2/4: Выбор дашбордов.") self.logger.info("[INFO][select_dashboards][ENTER] Шаг 2/4: Выбор дашбордов.")
try: try:
all_dashboards = self.from_c.get_dashboards() _, all_dashboards = self.from_c.get_dashboards()
if not all_dashboards: if not all_dashboards:
self.logger.warning("[WARN][select_dashboards][STATE] В исходном окружении не найдено дашбордов.") self.logger.warning("[WARN][select_dashboards][STATE] В исходном окружении не найдено дашбордов.")
print("В исходном окружении не найдено дашбордов.") w = Whiptail(title="Информация", backtitle="Superset Migration Tool")
w.msgbox("В исходном окружении не найдено дашбордов.")
return return
while True: w = Whiptail(title="Выбор дашбордов", backtitle="Superset Migration Tool")
print("\nДоступные дашборды:")
for i, dashboard in enumerate(all_dashboards):
print(f" {i + 1}. {dashboard['dashboard_title']}")
print("\nОпции:") dashboard_options = [(str(d['id']), d['dashboard_title']) for d in all_dashboards]
print(" - Введите номера дашбордов через запятую (например, 1, 3, 5).")
print(" - Введите 'все' для выбора всех дашбордов.")
print(" - Введите 'поиск <запрос>' для поиска дашбордов.")
print(" - Введите 'выход' для завершения.")
choice = input("Ваш выбор: ").lower().strip() (return_code, selected_ids) = w.checklist("Выберите дашборды для миграции:", dashboard_options)
if choice == 'выход': if return_code == 0:
break self.dashboards_to_migrate = [d for d in all_dashboards if str(d['id']) in selected_ids]
elif choice == 'все':
self.dashboards_to_migrate = all_dashboards
self.logger.info(f"[INFO][select_dashboards][STATE] Выбраны все дашборды: {len(self.dashboards_to_migrate)}")
break
elif choice.startswith('поиск '):
search_query = choice[6:].strip()
filtered_dashboards = [d for d in all_dashboards if search_query in d['dashboard_title'].lower()]
if not filtered_dashboards:
print("По вашему запросу ничего не найдено.")
else:
all_dashboards = filtered_dashboards
continue
else:
try:
selected_indices = [int(i.strip()) - 1 for i in choice.split(',')]
self.dashboards_to_migrate = [all_dashboards[i] for i in selected_indices if 0 <= i < len(all_dashboards)]
self.logger.info(f"[INFO][select_dashboards][STATE] Выбрано дашбордов: {len(self.dashboards_to_migrate)}") self.logger.info(f"[INFO][select_dashboards][STATE] Выбрано дашбордов: {len(self.dashboards_to_migrate)}")
break
except (ValueError, IndexError):
print("Неверный ввод. Пожалуйста, введите корректные номера.")
except Exception as e: except Exception as e:
self.logger.error(f"[ERROR][select_dashboards][FAILURE] Ошибка при получении или выборе дашбордов: {e}", exc_info=True) self.logger.error(f"[ERROR][select_dashboards][FAILURE] Ошибка при получении или выборе дашбордов: {e}", exc_info=True)
print("Произошла ошибка при работе с дашбордами.") w = Whiptail(title="Ошибка", backtitle="Superset Migration Tool")
w.msgbox("Произошла ошибка при работе с дашбордами.")
self.logger.info("[INFO][select_dashboards][EXIT] Шаг 2 завершен.") self.logger.info("[INFO][select_dashboards][EXIT] Шаг 2 завершен.")
# END_FUNCTION_select_dashboards # END_FUNCTION_select_dashboards
@@ -184,44 +148,20 @@ class Migration:
"""Шаг 3: Подтверждение и настройка замены конфигурации БД.""" """Шаг 3: Подтверждение и настройка замены конфигурации БД."""
self.logger.info("[INFO][confirm_db_config_replacement][ENTER] Шаг 3/4: Замена конфигурации БД.") self.logger.info("[INFO][confirm_db_config_replacement][ENTER] Шаг 3/4: Замена конфигурации БД.")
while True: w = Whiptail(title="Замена конфигурации БД", backtitle="Superset Migration Tool")
choice = input("Хотите ли вы заменить конфигурации баз данных в YAML-файлах? (да/нет): ").lower().strip() if w.yesno("Хотите ли вы заменить конфигурации баз данных в YAML-файлах?"):
if choice in ["да", "нет"]: (return_code, old_db_name) = w.inputbox("Введите имя заменяемой базы данных (например, db_dev):")
break if return_code != 0:
print("Неверный ввод. Пожалуйста, введите 'да' или 'нет'.")
if choice == 'нет':
self.logger.info("[INFO][confirm_db_config_replacement][STATE] Замена конфигурации БД пропущена.")
return return
# Эвристический расчет (return_code, new_db_name) = w.inputbox("Введите новое имя базы данных (например, db_prod):")
from_env = self.from_c.env.upper() if return_code != 0:
to_env = self.to_c.env.upper() return
heuristic_applied = False
if from_env == "DEV" and to_env == "PROD": self.db_config_replacement = {"old": {"database_name": old_db_name}, "new": {"database_name": new_db_name}}
self.db_config_replacement = {"old": {"database_name": "db_dev"}, "new": {"database_name": "db_prod"}} # Пример
self.logger.info("[INFO][confirm_db_config_replacement][STATE] Применена эвристика DEV -> PROD.")
heuristic_applied = True
elif from_env == "PROD" and to_env == "DEV":
self.db_config_replacement = {"old": {"database_name": "db_prod"}, "new": {"database_name": "db_dev"}} # Пример
self.logger.info("[INFO][confirm_db_config_replacement][STATE] Применена эвристика PROD -> DEV.")
heuristic_applied = True
if heuristic_applied:
print(f"На основе эвристики будет произведена следующая замена: {self.db_config_replacement}")
confirm = input("Подтверждаете? (да/нет): ").lower().strip()
if confirm != 'да':
self.db_config_replacement = None
heuristic_applied = False
if not heuristic_applied:
print("Пожалуйста, введите детали для замены.")
old_key = input("Ключ для замены (например, database_name): ")
old_value = input(f"Старое значение для {old_key}: ")
new_value = input(f"Новое значение для {old_key}: ")
self.db_config_replacement = {"old": {old_key: old_value}, "new": {old_key: new_value}}
self.logger.info(f"[INFO][confirm_db_config_replacement][STATE] Установлена ручная замена: {self.db_config_replacement}") self.logger.info(f"[INFO][confirm_db_config_replacement][STATE] Установлена ручная замена: {self.db_config_replacement}")
else:
self.logger.info("[INFO][confirm_db_config_replacement][STATE] Замена конфигурации БД пропущена.")
self.logger.info("[INFO][confirm_db_config_replacement][EXIT] Шаг 3 завершен.") self.logger.info("[INFO][confirm_db_config_replacement][EXIT] Шаг 3 завершен.")
# END_FUNCTION_confirm_db_config_replacement # END_FUNCTION_confirm_db_config_replacement
@@ -235,60 +175,54 @@ class Migration:
def execute_migration(self): def execute_migration(self):
"""Шаг 4: Выполнение миграции и обновления конфигураций.""" """Шаг 4: Выполнение миграции и обновления конфигураций."""
self.logger.info("[INFO][execute_migration][ENTER] Шаг 4/4: Выполнение миграции.") self.logger.info("[INFO][execute_migration][ENTER] Шаг 4/4: Выполнение миграции.")
w = Whiptail(title="Выполнение миграции", backtitle="Superset Migration Tool")
if not self.dashboards_to_migrate: if not self.dashboards_to_migrate:
self.logger.warning("[WARN][execute_migration][STATE] Нет дашбордов для миграции.") self.logger.warning("[WARN][execute_migration][STATE] Нет дашбордов для миграции.")
print("Нет дашбордов для миграции. Завершение.") w.msgbox("Нет дашбордов для миграции. Завершение.")
return return
db_configs_for_update = [] total_dashboards = len(self.dashboards_to_migrate)
if self.db_config_replacement: self.logger.info(f"[INFO][execute_migration][STATE] Начало миграции {total_dashboards} дашбордов.")
try: with w.gauge("Выполняется миграция...", width=60, height=10) as gauge:
from_dbs = self.from_c.get_databases() for i, dashboard in enumerate(self.dashboards_to_migrate):
to_dbs = self.to_c.get_databases()
# Просто пример, как можно было бы сопоставить базы данных.
# В реальном сценарии логика может быть сложнее.
for from_db in from_dbs:
for to_db in to_dbs:
# Предполагаем, что мы можем сопоставить базы по имени, заменив суффикс
if from_db['database_name'].replace(self.from_c.env.upper(), self.to_c.env.upper()) == to_db['database_name']:
db_configs_for_update.append({
"old": {"database_name": from_db['database_name']},
"new": {"database_name": to_db['database_name']}
})
self.logger.info(f"[INFO][execute_migration][STATE] Сформированы конфигурации для замены БД: {db_configs_for_update}")
except Exception as e:
self.logger.error(f"[ERROR][execute_migration][FAILURE] Не удалось получить конфигурации БД: {e}", exc_info=True)
print("Не удалось получить конфигурации БД. Миграция будет продолжена без замены.")
for dashboard in self.dashboards_to_migrate:
try: try:
dashboard_id = dashboard['id'] dashboard_id = dashboard['id']
self.logger.info(f"[INFO][execute_migration][PROGRESS] Миграция дашборда: {dashboard['dashboard_title']} (ID: {dashboard_id})") dashboard_title = dashboard['dashboard_title']
progress = int((i / total_dashboards) * 100)
self.logger.debug(f"[DEBUG][execute_migration][PROGRESS] {progress}% - Миграция: {dashboard_title}")
gauge.set_text(f"Миграция: {dashboard_title} ({i+1}/{total_dashboards})")
gauge.set_percent(progress)
self.logger.info(f"[INFO][execute_migration][PROGRESS] Миграция дашборда: {dashboard_title} (ID: {dashboard_id})")
# 1. Экспорт # 1. Экспорт
exported_content = self.from_c.export_dashboards(dashboard_id) exported_content, _ = self.from_c.export_dashboard(dashboard_id)
zip_path, unpacked_path = save_and_unpack_dashboard(exported_content, f"temp_export_{dashboard_id}", unpack=True) zip_path, unpacked_path = save_and_unpack_dashboard(exported_content, f"temp_export_{dashboard_id}", unpack=True)
self.logger.info(f"[INFO][execute_migration][STATE] Дашборд экспортирован и распакован в {unpacked_path}") self.logger.info(f"[INFO][execute_migration][STATE] Дашборд экспортирован и распакован в {unpacked_path}")
# 2. Обновление YAML, если нужно # 2. Обновление YAML, если нужно
if db_configs_for_update: if self.db_config_replacement:
update_yamls(db_configs=db_configs_for_update, path=str(unpacked_path)) update_yamls(db_configs=[self.db_config_replacement], path=str(unpacked_path))
self.logger.info(f"[INFO][execute_migration][STATE] YAML-файлы обновлены.") self.logger.info(f"[INFO][execute_migration][STATE] YAML-файлы обновлены.")
# 3. Упаковка и импорт # 3. Упаковка и импорт
new_zip_path = f"migrated_dashboard_{dashboard_id}.zip" new_zip_path = f"migrated_dashboard_{dashboard_id}.zip"
create_dashboard_export(new_zip_path, [unpacked_path]) create_dashboard_export(new_zip_path, [str(unpacked_path)])
content_to_import, _ = read_dashboard_from_disk(new_zip_path) self.to_c.import_dashboard(new_zip_path)
self.to_c.import_dashboards(content_to_import) self.logger.info(f"[INFO][execute_migration][SUCCESS] Дашборд {dashboard_title} успешно импортирован.")
self.logger.info(f"[INFO][execute_migration][SUCCESS] Дашборд {dashboard['dashboard_title']} успешно импортирован.")
except Exception as e: except Exception as e:
self.logger.error(f"[ERROR][execute_migration][FAILURE] Ошибка при миграции дашборда {dashboard['dashboard_title']}: {e}", exc_info=True) self.logger.error(f"[ERROR][execute_migration][FAILURE] Ошибка при миграции дашборда {dashboard_title}: {e}", exc_info=True)
print(f"Не удалось смигрировать дашборд: {dashboard['dashboard_title']}") error_msg = f"Не удалось смигрировать дашборд: {dashboard_title}.\n\nОшибка: {e}"
w.msgbox(error_msg, width=60, height=15)
gauge.set_percent(100)
self.logger.info("[INFO][execute_migration][STATE] Миграция завершена.")
w.msgbox("Миграция завершена!", width=40, height=8)
self.logger.info("[INFO][execute_migration][EXIT] Шаг 4 завершен.") self.logger.info("[INFO][execute_migration][EXIT] Шаг 4 завершен.")
# END_FUNCTION_execute_migration # END_FUNCTION_execute_migration

View File

@@ -2,3 +2,5 @@ pyyaml
requests requests
keyring keyring
urllib3 urllib3
pydantic
whiptail-dialogs

View File

@@ -41,6 +41,7 @@ class SupersetClient:
self.logger.info("[INFO][SupersetClient.__init__][ENTER] Initializing SupersetClient.") self.logger.info("[INFO][SupersetClient.__init__][ENTER] Initializing SupersetClient.")
self._validate_config(config) self._validate_config(config)
self.config = config self.config = config
self.env = config.env
self.network = APIClient( self.network = APIClient(
config=config.dict(), config=config.dict(),
verify_ssl=config.verify_ssl, verify_ssl=config.verify_ssl,
@@ -146,6 +147,12 @@ class SupersetClient:
return response_data.get("result", {}) return response_data.get("result", {})
# END_FUNCTION_get_dataset # END_FUNCTION_get_dataset
def get_databases(self) -> List[Dict]:
self.logger.info("[INFO][SupersetClient.get_databases][ENTER] Getting databases.")
response = self.network.request("GET", "/database/")
self.logger.info("[INFO][SupersetClient.get_databases][SUCCESS] Got databases.")
return response.get('result', [])
# [ENTITY: Function('export_dashboard')] # [ENTITY: Function('export_dashboard')]
# CONTRACT: # CONTRACT:
# PURPOSE: Экспорт дашборда в ZIP-архив. # PURPOSE: Экспорт дашборда в ZIP-архив.

View File

@@ -5,6 +5,7 @@
""" """
# [IMPORTS] Pydantic и Typing # [IMPORTS] Pydantic и Typing
import re
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from pydantic import BaseModel, validator, Field, HttpUrl, VERSION from pydantic import BaseModel, validator, Field, HttpUrl, VERSION
@@ -15,6 +16,7 @@ class SupersetConfig(BaseModel):
""" """
[CONFIG] Конфигурация подключения к Superset API. [CONFIG] Конфигурация подключения к Superset API.
""" """
env: str = Field(..., description="Название окружения (например, dev, prod).")
base_url: str = Field(..., description="Базовый URL Superset API, включая версию /api/v1.", pattern=r'.*/api/v1.*') base_url: str = Field(..., description="Базовый URL Superset API, включая версию /api/v1.", pattern=r'.*/api/v1.*')
auth: Dict[str, str] = Field(..., description="Словарь с данными для аутентификации (provider, username, password, refresh).") auth: Dict[str, str] = Field(..., description="Словарь с данными для аутентификации (provider, username, password, refresh).")
verify_ssl: bool = Field(True, description="Флаг для проверки SSL-сертификатов.") verify_ssl: bool = Field(True, description="Флаг для проверки SSL-сертификатов.")
@@ -45,15 +47,15 @@ class SupersetConfig(BaseModel):
# POSTCONDITIONS: Возвращает `v` если это валидный URL. # POSTCONDITIONS: Возвращает `v` если это валидный URL.
@validator('base_url') @validator('base_url')
def check_base_url_format(cls, v: str, values: dict) -> str: def check_base_url_format(cls, v: str, values: dict) -> str:
logger = values.get('logger') or SupersetLogger(name="SupersetConfig") """
logger.debug("[DEBUG][SupersetConfig.check_base_url_format][ENTER] Validating base_url.") Простейшая проверка:
try: - начинается с http/https,
if VERSION.startswith('1'): - содержит «/api/v1»,
HttpUrl(v) - не содержит пробельных символов в начале/конце.
except (ValueError, TypeError) as exc: """
logger.error("[ERROR][SupersetConfig.check_base_url_format][FAILURE] Invalid base_url format.") v = v.strip() # устраняем скрытые пробелы/переносы
raise ValueError(f"Invalid URL format: {v}") from exc if not re.fullmatch(r'https?://.+/api/v1/?(?:.*)?', v):
logger.debug("[DEBUG][SupersetConfig.check_base_url_format][SUCCESS] base_url validated.") raise ValueError(f"Invalid URL format: {v}")
return v return v
# END_FUNCTION_check_base_url_format # END_FUNCTION_check_base_url_format

View File

@@ -34,10 +34,10 @@ def setup_clients(logger: SupersetLogger) -> Dict[str, SupersetClient]:
clients = {} clients = {}
environments = { environments = {
"dev": "https://devta.bi.dwh.rusal.com/api/v1", "dev": "https://devta.bi.dwh.rusal.com/api/v1/",
"prod": "https://prodta.bi.dwh.rusal.com/api/v1", "prod": "https://prodta.bi.dwh.rusal.com/api/v1/",
"sbx": "https://sandboxta.bi.dwh.rusal.com/api/v1", "sbx": "https://sandboxta.bi.dwh.rusal.com/api/v1/",
"preprod": "https://preprodta.bi.dwh.rusal.com/api/v1" "preprod": "https://preprodta.bi.dwh.rusal.com/api/v1/"
} }
try: try:
@@ -48,6 +48,7 @@ def setup_clients(logger: SupersetLogger) -> Dict[str, SupersetClient]:
raise ValueError(f"Пароль для '{env_name} migrate' не найден в keyring.") raise ValueError(f"Пароль для '{env_name} migrate' не найден в keyring.")
config = SupersetConfig( config = SupersetConfig(
env=env_name,
base_url=base_url, base_url=base_url,
auth={ auth={
"provider": "db", "provider": "db",

View File

@@ -32,6 +32,7 @@
<MODULE path="superset_tool/client.py" id="mod_client"> <MODULE path="superset_tool/client.py" id="mod_client">
<PURPOSE>Клиент для взаимодействия с Superset API.</PURPOSE> <PURPOSE>Клиент для взаимодействия с Superset API.</PURPOSE>
<ENTITY type="Class" name="SupersetClient" id="class_superset_client"/> <ENTITY type="Class" name="SupersetClient" id="class_superset_client"/>
<ENTITY type="Function" name="get_databases" id="func_get_databases"/>
</MODULE> </MODULE>
<MODULE path="superset_tool/exceptions.py" id="mod_exceptions"> <MODULE path="superset_tool/exceptions.py" id="mod_exceptions">
<PURPOSE>Пользовательские исключения для Superset Tool.</PURPOSE> <PURPOSE>Пользовательские исключения для Superset Tool.</PURPOSE>
@@ -76,6 +77,7 @@
<NODE id="mod_logger" type="Module" label="Конфигурация логгера."/> <NODE id="mod_logger" type="Module" label="Конфигурация логгера."/>
<NODE id="mod_network" type="Module" label="Сетевые утилиты."/> <NODE id="mod_network" type="Module" label="Сетевые утилиты."/>
<NODE id="class_superset_client" type="Class" label="Клиент Superset."/> <NODE id="class_superset_client" type="Class" label="Клиент Superset."/>
<NODE id="func_get_databases" type="Function" label="Получение списка баз данных."/>
<NODE id="func_process_yaml_value" type="Function" label="(HELPER) Рекурсивно обрабатывает значения в YAML-структуре."/> <NODE id="func_process_yaml_value" type="Function" label="(HELPER) Рекурсивно обрабатывает значения в YAML-структуре."/>
<NODE id="func_update_yaml_file" type="Function" label="(HELPER) Обновляет один YAML файл."/> <NODE id="func_update_yaml_file" type="Function" label="(HELPER) Обновляет один YAML файл."/>
<NODE id="class_migration" type="Class" label="Инкапсулирует логику и состояние процесса миграции."/> <NODE id="class_migration" type="Class" label="Инкапсулирует логику и состояние процесса миграции."/>
@@ -90,6 +92,7 @@
<EDGE source_id="mod_superset_tool" target_id="mod_models" relation="CONTAINS"/> <EDGE source_id="mod_superset_tool" target_id="mod_models" relation="CONTAINS"/>
<EDGE source_id="mod_superset_tool" target_id="mod_utils" relation="CONTAINS"/> <EDGE source_id="mod_superset_tool" target_id="mod_utils" relation="CONTAINS"/>
<EDGE source_id="mod_client" target_id="class_superset_client" relation="CONTAINS"/> <EDGE source_id="mod_client" target_id="class_superset_client" relation="CONTAINS"/>
<EDGE source_id="class_superset_client" target_id="func_get_databases" relation="CONTAINS"/>
<EDGE source_id="mod_utils" target_id="mod_fileio" relation="CONTAINS"/> <EDGE source_id="mod_utils" target_id="mod_fileio" relation="CONTAINS"/>
<EDGE source_id="mod_utils" target_id="mod_init_clients" relation="CONTAINS"/> <EDGE source_id="mod_utils" target_id="mod_init_clients" relation="CONTAINS"/>
<EDGE source_id="mod_utils" target_id="mod_logger" relation="CONTAINS"/> <EDGE source_id="mod_utils" target_id="mod_logger" relation="CONTAINS"/>

28200
tech_spec/openapi.json Normal file

File diff suppressed because it is too large Load Diff