write to file in search script
This commit is contained in:
346
search_script.py
346
search_script.py
@@ -1,150 +1,206 @@
|
|||||||
# <GRACE_MODULE id="search_script" name="search_script.py">
|
# <GRACE_MODULE id="search_script" name="search_script.py">
|
||||||
# @SEMANTICS: search, superset, dataset, regex
|
# @SEMANTICS: search, superset, dataset, regex, file_output
|
||||||
# @PURPOSE: Предоставляет утилиты для поиска по текстовым паттернам в метаданных датасетов Superset.
|
# @PURPOSE: Предоставляет утилиты для поиска по текстовым паттернам в метаданных датасетов Superset.
|
||||||
# @DEPENDS_ON: superset_tool.client -> Для взаимодействия с API Superset.
|
# @DEPENDS_ON: superset_tool.client -> Для взаимодействия с API Superset.
|
||||||
# @DEPENDS_ON: superset_tool.utils -> Для логирования и инициализации клиентов.
|
# @DEPENDS_ON: superset_tool.utils -> Для логирования и инициализации клиентов.
|
||||||
|
|
||||||
# <IMPORTS>
|
# <IMPORTS>
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Dict, Optional
|
import os
|
||||||
from requests.exceptions import RequestException
|
from typing import Dict, Optional
|
||||||
from superset_tool.client import SupersetClient
|
from requests.exceptions import RequestException
|
||||||
from superset_tool.exceptions import SupersetAPIError
|
from superset_tool.client import SupersetClient
|
||||||
from superset_tool.utils.logger import SupersetLogger
|
from superset_tool.exceptions import SupersetAPIError
|
||||||
from superset_tool.utils.init_clients import setup_clients
|
from superset_tool.utils.logger import SupersetLogger
|
||||||
# </IMPORTS>
|
from superset_tool.utils.init_clients import setup_clients
|
||||||
|
# </IMPORTS>
|
||||||
# --- Начало кода модуля ---
|
|
||||||
|
# --- Начало кода модуля ---
|
||||||
# <ANCHOR id="search_datasets" type="Function">
|
|
||||||
# @PURPOSE: Выполняет поиск по строковому паттерну в метаданных всех датасетов.
|
# <ANCHOR id="search_datasets" type="Function">
|
||||||
# @PRE: `client` должен быть инициализированным экземпляром `SupersetClient`.
|
# @PURPOSE: Выполняет поиск по строковому паттерну в метаданных всех датасетов.
|
||||||
# @PRE: `search_pattern` должен быть валидной строкой регулярного выражения.
|
# @PRE: `client` должен быть инициализированным экземпляром `SupersetClient`.
|
||||||
# @POST: Возвращает словарь с результатами поиска, где ключ - ID датасета, значение - список совпадений.
|
# @PRE: `search_pattern` должен быть валидной строкой регулярного выражения.
|
||||||
# @PARAM: client: SupersetClient - Клиент для доступа к API Superset.
|
# @POST: Возвращает словарь с результатами поиска, где ключ - ID датасета, значение - список совпадений.
|
||||||
# @PARAM: search_pattern: str - Регулярное выражение для поиска.
|
# @PARAM: client: SupersetClient - Клиент для доступа к API Superset.
|
||||||
# @PARAM: logger: Optional[SupersetLogger] - Инстанс логгера.
|
# @PARAM: search_pattern: str - Регулярное выражение для поиска.
|
||||||
# @RETURN: Optional[Dict] - Словарь с результатами или None, если ничего не найдено.
|
# @PARAM: logger: Optional[SupersetLogger] - Инстанс логгера.
|
||||||
# @THROW: re.error - Если паттерн регулярного выражения невалиден.
|
# @RETURN: Optional[Dict] - Словарь с результатами или None, если ничего не найдено.
|
||||||
# @THROW: SupersetAPIError, RequestException - При критических ошибках API.
|
# @THROW: re.error - Если паттерн регулярного выражения невалиден.
|
||||||
# @RELATION: CALLS -> client.get_datasets
|
# @THROW: SupersetAPIError, RequestException - При критических ошибках API.
|
||||||
def search_datasets(
|
# @RELATION: CALLS -> client.get_datasets
|
||||||
client: SupersetClient,
|
def search_datasets(
|
||||||
search_pattern: str,
|
client: SupersetClient,
|
||||||
logger: Optional[SupersetLogger] = None
|
search_pattern: str,
|
||||||
) -> Optional[Dict]:
|
logger: Optional[SupersetLogger] = None
|
||||||
logger = logger or SupersetLogger(name="dataset_search")
|
) -> Optional[Dict]:
|
||||||
logger.info(f"[search_datasets][Enter] Searching for pattern: '{search_pattern}'")
|
logger = logger or SupersetLogger(name="dataset_search")
|
||||||
try:
|
logger.info(f"[search_datasets][Enter] Searching for pattern: '{search_pattern}'")
|
||||||
_, datasets = client.get_datasets(query={"columns": ["id", "table_name", "sql", "database", "columns"]})
|
try:
|
||||||
|
_, datasets = client.get_datasets(query={"columns": ["id", "table_name", "sql", "database", "columns"]})
|
||||||
if not datasets:
|
|
||||||
logger.warning("[search_datasets][State] No datasets found.")
|
if not datasets:
|
||||||
return None
|
logger.warning("[search_datasets][State] No datasets found.")
|
||||||
|
return None
|
||||||
pattern = re.compile(search_pattern, re.IGNORECASE)
|
|
||||||
results = {}
|
pattern = re.compile(search_pattern, re.IGNORECASE)
|
||||||
|
results = {}
|
||||||
for dataset in datasets:
|
|
||||||
dataset_id = dataset.get('id')
|
for dataset in datasets:
|
||||||
if not dataset_id:
|
dataset_id = dataset.get('id')
|
||||||
continue
|
if not dataset_id:
|
||||||
|
continue
|
||||||
matches = []
|
|
||||||
for field, value in dataset.items():
|
matches = []
|
||||||
value_str = str(value)
|
for field, value in dataset.items():
|
||||||
if pattern.search(value_str):
|
value_str = str(value)
|
||||||
match_obj = pattern.search(value_str)
|
if pattern.search(value_str):
|
||||||
matches.append({
|
match_obj = pattern.search(value_str)
|
||||||
"field": field,
|
matches.append({
|
||||||
"match": match_obj.group() if match_obj else "",
|
"field": field,
|
||||||
"value": value_str
|
"match": match_obj.group() if match_obj else "",
|
||||||
})
|
"value": value_str
|
||||||
|
})
|
||||||
if matches:
|
|
||||||
results[dataset_id] = matches
|
if matches:
|
||||||
|
results[dataset_id] = matches
|
||||||
logger.info(f"[search_datasets][Success] Found matches in {len(results)} datasets.")
|
|
||||||
return results
|
logger.info(f"[search_datasets][Success] Found matches in {len(results)} datasets.")
|
||||||
|
return results
|
||||||
except re.error as e:
|
|
||||||
logger.error(f"[search_datasets][Failure] Invalid regex pattern: {e}", exc_info=True)
|
except re.error as e:
|
||||||
raise
|
logger.error(f"[search_datasets][Failure] Invalid regex pattern: {e}", exc_info=True)
|
||||||
except (SupersetAPIError, RequestException) as e:
|
raise
|
||||||
logger.critical(f"[search_datasets][Failure] Critical error during search: {e}", exc_info=True)
|
except (SupersetAPIError, RequestException) as e:
|
||||||
raise
|
logger.critical(f"[search_datasets][Failure] Critical error during search: {e}", exc_info=True)
|
||||||
# </ANCHOR id="search_datasets">
|
raise
|
||||||
|
# </ANCHOR id="search_datasets">
|
||||||
# <ANCHOR id="print_search_results" type="Function">
|
|
||||||
# @PURPOSE: Форматирует результаты поиска для читаемого вывода в консоль.
|
# <ANCHOR id="save_results_to_file" type="Function">
|
||||||
# @PRE: `results` является словарем, возвращенным `search_datasets`, или `None`.
|
# @PURPOSE: Сохраняет результаты поиска в текстовый файл.
|
||||||
# @POST: Возвращает отформатированную строку с результатами.
|
# @PRE: `results` является словарем, возвращенным `search_datasets`, или `None`.
|
||||||
# @PARAM: results: Optional[Dict] - Словарь с результатами поиска.
|
# @PRE: `filename` должен быть допустимым путем к файлу.
|
||||||
# @PARAM: context_lines: int - Количество строк контекста для вывода до и после совпадения.
|
# @POST: Записывает отформатированные результаты в указанный файл.
|
||||||
# @RETURN: str - Отформатированный отчет.
|
# @PARAM: results: Optional[Dict] - Словарь с результатами поиска.
|
||||||
def print_search_results(results: Optional[Dict], context_lines: int = 3) -> str:
|
# @PARAM: filename: str - Имя файла для сохранения результатов.
|
||||||
if not results:
|
# @PARAM: logger: Optional[SupersetLogger] - Инстанс логгера.
|
||||||
return "Ничего не найдено"
|
# @RETURN: bool - Успешно ли выполнено сохранение.
|
||||||
|
def save_results_to_file(results: Optional[Dict], filename: str, logger: Optional[SupersetLogger] = None) -> bool:
|
||||||
output = []
|
logger = logger or SupersetLogger(name="file_writer")
|
||||||
for dataset_id, matches in results.items():
|
logger.info(f"[save_results_to_file][Enter] Saving results to file: {filename}")
|
||||||
output.append(f"\n--- Dataset ID: {dataset_id} ---")
|
try:
|
||||||
|
formatted_report = print_search_results(results)
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(formatted_report)
|
||||||
|
logger.info(f"[save_results_to_file][Success] Results saved to {filename}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[save_results_to_file][Failure] Failed to save results to file: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
# </ANCHOR id="save_results_to_file">
|
||||||
|
|
||||||
|
# <ANCHOR id="print_search_results" type="Function">
|
||||||
|
# @PURPOSE: Форматирует результаты поиска для читаемого вывода в консоль.
|
||||||
|
# @PRE: `results` является словарем, возвращенным `search_datasets`, или `None`.
|
||||||
|
# @POST: Возвращает отформатированную строку с результатами.
|
||||||
|
# @PARAM: results: Optional[Dict] - Словарь с результатами поиска.
|
||||||
|
# @PARAM: context_lines: int - Количество строк контекста для вывода до и после совпадения.
|
||||||
|
# @RETURN: str - Отформатированный отчет.
|
||||||
|
def print_search_results(results: Optional[Dict], context_lines: int = 3) -> str:
|
||||||
|
if not results:
|
||||||
|
return "Ничего не найдено"
|
||||||
|
|
||||||
|
output = []
|
||||||
|
for dataset_id, matches in results.items():
|
||||||
|
# Получаем информацию о базе данных для текущего датасета
|
||||||
|
database_info = ""
|
||||||
|
# Ищем поле database среди совпадений, чтобы вывести его
|
||||||
for match_info in matches:
|
for match_info in matches:
|
||||||
field, match_text, full_value = match_info['field'], match_info['match'], match_info['value']
|
if match_info['field'] == 'database':
|
||||||
output.append(f" - Поле: {field}")
|
database_info = match_info['value']
|
||||||
output.append(f" Совпадение: '{match_text}'")
|
break
|
||||||
|
# Если database не найден в совпадениях, пробуем получить из других полей
|
||||||
|
if not database_info:
|
||||||
|
# Предполагаем, что база данных может быть в одном из полей, например sql или table_name
|
||||||
|
# Но для точности лучше использовать специальное поле, которое мы уже получили
|
||||||
|
pass # Пока не выводим, если не нашли явно
|
||||||
|
|
||||||
lines = full_value.splitlines()
|
output.append(f"\n--- Dataset ID: {dataset_id} ---")
|
||||||
if not lines: continue
|
if database_info:
|
||||||
|
output.append(f" Database: {database_info}")
|
||||||
|
output.append("") # Пустая строка для читабельности
|
||||||
|
|
||||||
|
for match_info in matches:
|
||||||
|
field, match_text, full_value = match_info['field'], match_info['match'], match_info['value']
|
||||||
|
output.append(f" - Поле: {field}")
|
||||||
|
output.append(f" Совпадение: '{match_text}'")
|
||||||
|
|
||||||
|
lines = full_value.splitlines()
|
||||||
|
if not lines: continue
|
||||||
|
|
||||||
|
match_line_index = -1
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if match_text in line:
|
||||||
|
match_line_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if match_line_index != -1:
|
||||||
|
start = max(0, match_line_index - context_lines)
|
||||||
|
end = min(len(lines), match_line_index + context_lines + 1)
|
||||||
|
output.append(" Контекст:")
|
||||||
|
for i in range(start, end):
|
||||||
|
prefix = f"{i + 1:5d}: "
|
||||||
|
line_content = lines[i]
|
||||||
|
if i == match_line_index:
|
||||||
|
highlighted = line_content.replace(match_text, f">>>{match_text}<<<")
|
||||||
|
output.append(f" {prefix}{highlighted}")
|
||||||
|
else:
|
||||||
|
output.append(f" {prefix}{line_content}")
|
||||||
|
output.append("-" * 25)
|
||||||
|
return "\n".join(output)
|
||||||
|
# </ANCHOR id="print_search_results">
|
||||||
|
|
||||||
|
# <ANCHOR id="main" type="Function">
|
||||||
|
# @PURPOSE: Основная точка входа для запуска скрипта поиска.
|
||||||
|
# @RELATION: CALLS -> setup_clients
|
||||||
|
# @RELATION: CALLS -> search_datasets
|
||||||
|
# @RELATION: CALLS -> print_search_results
|
||||||
|
# @RELATION: CALLS -> save_results_to_file
|
||||||
|
def main():
|
||||||
|
logger = SupersetLogger(level=logging.INFO, console=True)
|
||||||
|
clients = setup_clients(logger)
|
||||||
|
|
||||||
|
target_client = clients['prod']
|
||||||
|
search_query = r"from dm_view.[a-z_]*"
|
||||||
|
|
||||||
match_line_index = -1
|
# Генерируем имя файла на основе времени
|
||||||
for i, line in enumerate(lines):
|
import datetime
|
||||||
if match_text in line:
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
match_line_index = i
|
output_filename = f"search_results_{timestamp}.txt"
|
||||||
break
|
|
||||||
|
results = search_datasets(
|
||||||
if match_line_index != -1:
|
client=target_client,
|
||||||
start = max(0, match_line_index - context_lines)
|
search_pattern=search_query,
|
||||||
end = min(len(lines), match_line_index + context_lines + 1)
|
logger=logger
|
||||||
output.append(" Контекст:")
|
)
|
||||||
for i in range(start, end):
|
|
||||||
prefix = f"{i + 1:5d}: "
|
report = print_search_results(results)
|
||||||
line_content = lines[i]
|
|
||||||
if i == match_line_index:
|
logger.info(f"[main][Success] Search finished. Report:\n{report}")
|
||||||
highlighted = line_content.replace(match_text, f">>>{match_text}<<<")
|
|
||||||
output.append(f" {prefix}{highlighted}")
|
# Сохраняем результаты в файл
|
||||||
else:
|
success = save_results_to_file(results, output_filename, logger)
|
||||||
output.append(f" {prefix}{line_content}")
|
if success:
|
||||||
output.append("-" * 25)
|
logger.info(f"[main][Success] Results also saved to file: {output_filename}")
|
||||||
return "\n".join(output)
|
else:
|
||||||
# </ANCHOR id="print_search_results">
|
logger.error(f"[main][Failure] Failed to save results to file: {output_filename}")
|
||||||
|
|
||||||
# <ANCHOR id="main" type="Function">
|
# </ANCHOR id="main">
|
||||||
# @PURPOSE: Основная точка входа для запуска скрипта поиска.
|
|
||||||
# @RELATION: CALLS -> setup_clients
|
if __name__ == "__main__":
|
||||||
# @RELATION: CALLS -> search_datasets
|
main()
|
||||||
# @RELATION: CALLS -> print_search_results
|
|
||||||
def main():
|
# --- Конец кода модуля ---
|
||||||
logger = SupersetLogger(level=logging.INFO, console=True)
|
|
||||||
clients = setup_clients(logger)
|
# </GRACE_MODULE id="search_script">
|
||||||
|
|
||||||
target_client = clients['prod']
|
|
||||||
search_query = r".account_balance_by_contract"
|
|
||||||
|
|
||||||
results = search_datasets(
|
|
||||||
client=target_client,
|
|
||||||
search_pattern=search_query,
|
|
||||||
logger=logger
|
|
||||||
)
|
|
||||||
|
|
||||||
report = print_search_results(results)
|
|
||||||
logger.info(f"[main][Success] Search finished. Report:\n{report}")
|
|
||||||
# </ANCHOR id="main">
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
# --- Конец кода модуля ---
|
|
||||||
|
|
||||||
# </GRACE_MODULE id="search_script">
|
|
||||||
Reference in New Issue
Block a user