fix url check
This commit is contained in:
195
.github/instructions/fractal_promt.instructions.md
vendored
195
.github/instructions/fractal_promt.instructions.md
vendored
@@ -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
5
.gitignore
vendored
@@ -2,5 +2,6 @@
|
|||||||
*.ps1
|
*.ps1
|
||||||
keyring passwords.py
|
keyring passwords.py
|
||||||
*logs*
|
*logs*
|
||||||
*\.github*
|
*github*
|
||||||
|
*venv*
|
||||||
|
*git*
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</ФИЛОСОФИЯ>
|
</ФИЛОСОФИЯ>
|
||||||
|
|
||||||
<КАРТА_ПРОЕКТА>
|
<КАРТА_ПРОЕКТА>
|
||||||
<ИМЯ_ФАЙЛА>PROJECT_SEMANTICS.xml</ИМЯ_ФАЙЛА>
|
<ИМЯ_ФАЙЛА>tech_spec/PROJECT_SEMANTICS.xml</ИМЯ_ФАЙЛА>
|
||||||
<НАЗНАЧЕНИЕ>
|
<НАЗНАЧЕНИЕ>
|
||||||
Этот файл является единым источником истины (Single Source of Truth) о семантической структуре всего проекта. Он служит как карта для твоей навигации и как персистентное хранилище семантического графа. Ты обязан загружать его в начале каждой сессии и обновлять в конце.
|
Этот файл является единым источником истины (Single Source of Truth) о семантической структуре всего проекта. Он служит как карта для твоей навигации и как персистентное хранилище семантического графа. Ты обязан загружать его в начале каждой сессии и обновлять в конце.
|
||||||
</НАЗНАЧЕНИЕ>
|
</НАЗНАЧЕНИЕ>
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"}
|
try:
|
||||||
|
all_clients = setup_clients(self.logger)
|
||||||
|
available_envs = list(all_clients.keys())
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"[ERROR][select_environments][FAILURE] Ошибка при инициализации клиентов: {e}", exc_info=True)
|
||||||
|
w = Whiptail(title="Ошибка", backtitle="Superset Migration Tool")
|
||||||
|
w.msgbox("Не удалось инициализировать клиенты. Проверьте конфигурацию.")
|
||||||
|
return
|
||||||
|
|
||||||
print("Доступные окружения:")
|
w = Whiptail(title="Выбор окружения", backtitle="Superset Migration Tool")
|
||||||
for key, value in available_envs.items():
|
|
||||||
print(f" {key}. {value}")
|
|
||||||
|
|
||||||
while self.from_c is None:
|
|
||||||
try:
|
|
||||||
from_env_choice = input("Выберите исходное окружение (номер): ")
|
|
||||||
from_env_name = available_envs.get(from_env_choice)
|
|
||||||
if not from_env_name:
|
|
||||||
print("Неверный выбор. Попробуйте снова.")
|
|
||||||
continue
|
|
||||||
|
|
||||||
clients = init_superset_clients(self.logger, env=from_env_name.lower())
|
|
||||||
self.from_c = clients[0]
|
|
||||||
self.logger.info(f"[INFO][select_environments][STATE] Исходное окружение: {from_env_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"[ERROR][select_environments][FAILURE] Ошибка при инициализации клиента-источника: {e}", exc_info=True)
|
|
||||||
print("Не удалось инициализировать клиент. Проверьте конфигурацию.")
|
|
||||||
|
|
||||||
while self.to_c is None:
|
# Select source environment
|
||||||
try:
|
(return_code, from_env_name) = w.menu("Выберите исходное окружение:", available_envs)
|
||||||
to_env_choice = input("Выберите целевое окружение (номер): ")
|
if return_code == 0:
|
||||||
to_env_name = available_envs.get(to_env_choice)
|
self.from_c = all_clients[from_env_name]
|
||||||
|
self.logger.info(f"[INFO][select_environments][STATE] Исходное окружение: {from_env_name}")
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
if not to_env_name:
|
# Select target environment
|
||||||
print("Неверный выбор. Попробуйте снова.")
|
available_envs.remove(from_env_name)
|
||||||
continue
|
(return_code, to_env_name) = w.menu("Выберите целевое окружение:", available_envs)
|
||||||
|
if return_code == 0:
|
||||||
if to_env_name == self.from_c.env:
|
self.to_c = all_clients[to_env_name]
|
||||||
print("Целевое и исходное окружения не могут совпадать.")
|
self.logger.info(f"[INFO][select_environments][STATE] Целевое окружение: {to_env_name}")
|
||||||
continue
|
else:
|
||||||
|
return
|
||||||
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}")
|
|
||||||
|
|
||||||
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):
|
dashboard_options = [(str(d['id']), d['dashboard_title']) for d in all_dashboards]
|
||||||
print(f" {i + 1}. {dashboard['dashboard_title']}")
|
|
||||||
|
(return_code, selected_ids) = w.checklist("Выберите дашборды для миграции:", dashboard_options)
|
||||||
print("\nОпции:")
|
|
||||||
print(" - Введите номера дашбордов через запятую (например, 1, 3, 5).")
|
|
||||||
print(" - Введите 'все' для выбора всех дашбордов.")
|
|
||||||
print(" - Введите 'поиск <запрос>' для поиска дашбордов.")
|
|
||||||
print(" - Введите 'выход' для завершения.")
|
|
||||||
|
|
||||||
choice = input("Ваш выбор: ").lower().strip()
|
if return_code == 0:
|
||||||
|
self.dashboards_to_migrate = [d for d in all_dashboards if str(d['id']) in selected_ids]
|
||||||
if choice == 'выход':
|
self.logger.info(f"[INFO][select_dashboards][STATE] Выбрано дашбордов: {len(self.dashboards_to_migrate)}")
|
||||||
break
|
|
||||||
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)}")
|
|
||||||
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("Неверный ввод. Пожалуйста, введите 'да' или 'нет'.")
|
return
|
||||||
|
|
||||||
|
(return_code, new_db_name) = w.inputbox("Введите новое имя базы данных (например, db_prod):")
|
||||||
|
if return_code != 0:
|
||||||
|
return
|
||||||
|
|
||||||
if choice == 'нет':
|
self.db_config_replacement = {"old": {"database_name": old_db_name}, "new": {"database_name": new_db_name}}
|
||||||
self.logger.info("[INFO][confirm_db_config_replacement][STATE] Замена конфигурации БД пропущена.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Эвристический расчет
|
|
||||||
from_env = self.from_c.env.upper()
|
|
||||||
to_env = self.to_c.env.upper()
|
|
||||||
heuristic_applied = False
|
|
||||||
|
|
||||||
if from_env == "DEV" and to_env == "PROD":
|
|
||||||
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()
|
try:
|
||||||
|
dashboard_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. Экспорт
|
||||||
|
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)
|
||||||
|
self.logger.info(f"[INFO][execute_migration][STATE] Дашборд экспортирован и распакован в {unpacked_path}")
|
||||||
|
|
||||||
|
# 2. Обновление YAML, если нужно
|
||||||
|
if self.db_config_replacement:
|
||||||
|
update_yamls(db_configs=[self.db_config_replacement], path=str(unpacked_path))
|
||||||
|
self.logger.info(f"[INFO][execute_migration][STATE] YAML-файлы обновлены.")
|
||||||
|
|
||||||
|
# 3. Упаковка и импорт
|
||||||
|
new_zip_path = f"migrated_dashboard_{dashboard_id}.zip"
|
||||||
|
create_dashboard_export(new_zip_path, [str(unpacked_path)])
|
||||||
|
|
||||||
|
self.to_c.import_dashboard(new_zip_path)
|
||||||
|
self.logger.info(f"[INFO][execute_migration][SUCCESS] Дашборд {dashboard_title} успешно импортирован.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"[ERROR][execute_migration][FAILURE] Ошибка при миграции дашборда {dashboard_title}: {e}", exc_info=True)
|
||||||
|
error_msg = f"Не удалось смигрировать дашборд: {dashboard_title}.\n\nОшибка: {e}"
|
||||||
|
w.msgbox(error_msg, width=60, height=15)
|
||||||
|
|
||||||
# Просто пример, как можно было бы сопоставить базы данных.
|
gauge.set_percent(100)
|
||||||
# В реальном сценарии логика может быть сложнее.
|
|
||||||
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:
|
|
||||||
dashboard_id = dashboard['id']
|
|
||||||
self.logger.info(f"[INFO][execute_migration][PROGRESS] Миграция дашборда: {dashboard['dashboard_title']} (ID: {dashboard_id})")
|
|
||||||
|
|
||||||
# 1. Экспорт
|
|
||||||
exported_content = self.from_c.export_dashboards(dashboard_id)
|
|
||||||
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}")
|
|
||||||
|
|
||||||
# 2. Обновление YAML, если нужно
|
|
||||||
if db_configs_for_update:
|
|
||||||
update_yamls(db_configs=db_configs_for_update, path=str(unpacked_path))
|
|
||||||
self.logger.info(f"[INFO][execute_migration][STATE] YAML-файлы обновлены.")
|
|
||||||
|
|
||||||
# 3. Упаковка и импорт
|
|
||||||
new_zip_path = f"migrated_dashboard_{dashboard_id}.zip"
|
|
||||||
create_dashboard_export(new_zip_path, [unpacked_path])
|
|
||||||
|
|
||||||
content_to_import, _ = read_dashboard_from_disk(new_zip_path)
|
|
||||||
self.to_c.import_dashboards(content_to_import)
|
|
||||||
self.logger.info(f"[INFO][execute_migration][SUCCESS] Дашборд {dashboard['dashboard_title']} успешно импортирован.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"[ERROR][execute_migration][FAILURE] Ошибка при миграции дашборда {dashboard['dashboard_title']}: {e}", exc_info=True)
|
|
||||||
print(f"Не удалось смигрировать дашборд: {dashboard['dashboard_title']}")
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
pyyaml
|
pyyaml
|
||||||
requests
|
requests
|
||||||
keyring
|
keyring
|
||||||
urllib3
|
urllib3
|
||||||
|
pydantic
|
||||||
|
whiptail-dialogs
|
||||||
@@ -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-архив.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
28200
tech_spec/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user