This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Тестовый модуль для проверки загрузки описания категории.
|
||||
|
||||
Использование (из корня проекта):
|
||||
python tools/dev/volk_category_testing.py <category_id>
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
import socket
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import URLError, HTTPError
|
||||
from dotenv import load_dotenv
|
||||
|
||||
_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
load_dotenv(os.path.join(_PROJECT_ROOT, ".env"))
|
||||
load_dotenv()
|
||||
|
||||
# Параметры из .env
|
||||
VOLK_CATEGORY_DESC_PREFIX = os.getenv('VOLK_CATEGORY_DESC_PREFIX')
|
||||
VOLK_API_VERSION = os.getenv('VOLK_API_VERSION')
|
||||
|
||||
# Лог-файл для тестирования
|
||||
TEST_LOG_FILE = 'volk_category_testing.log'
|
||||
|
||||
def log_message(message, category="TEST"):
|
||||
"""Функция для записи в лог-файл с повторными попытками при блокировке"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_entry = f"[{timestamp}] [{category}] {message}\n"
|
||||
|
||||
# До 5 попыток записи с увеличением задержки
|
||||
for attempt in range(5):
|
||||
try:
|
||||
with open(TEST_LOG_FILE, 'a', encoding='utf-8') as f:
|
||||
f.write(log_entry)
|
||||
return True
|
||||
except (IOError, OSError) as e:
|
||||
if attempt < 4: # Не последняя попытка
|
||||
time.sleep(0.4) # Задержка 400ms между попытками
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def safe_log_direct(message, category="TEST"):
|
||||
"""Безопасная функция для прямой записи в лог-файл без обработки исключений
|
||||
Используется в критических ситуациях, когда обычный log_message может не сработать"""
|
||||
try:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_entry = f"[{timestamp}] [{category}] {message}\n"
|
||||
# Прямая запись без контекстного менеджера для максимальной надежности
|
||||
f = open(TEST_LOG_FILE, 'a', encoding='utf-8')
|
||||
try:
|
||||
f.write(log_entry)
|
||||
f.flush() # Принудительно сбрасываем буфер
|
||||
finally:
|
||||
f.close()
|
||||
except:
|
||||
# Игнорируем все исключения - эта функция должна быть максимально безопасной
|
||||
pass
|
||||
|
||||
def test_category_description_load(category_id, category_number=None, total_count=None):
|
||||
"""Тестирует загрузку описания категории
|
||||
|
||||
Args:
|
||||
category_id: ID категории для тестирования
|
||||
category_number: Номер категории в последовательности (опционально)
|
||||
total_count: Общее количество категорий (опционально)
|
||||
"""
|
||||
if category_number and total_count:
|
||||
header = f"НАЧАЛО ТЕСТИРОВАНИЯ [{category_number}/{total_count}] КАТЕГОРИИ: {category_id}"
|
||||
else:
|
||||
header = f"НАЧАЛО ТЕСТИРОВАНИЯ ЗАГРУЗКИ ОПИСАНИЯ КАТЕГОРИИ: {category_id}"
|
||||
|
||||
log_message("=" * 80)
|
||||
log_message(header)
|
||||
log_message("=" * 80)
|
||||
|
||||
# Проверка наличия префикса URL
|
||||
if not VOLK_CATEGORY_DESC_PREFIX:
|
||||
error_msg = "Переменная окружения VOLK_CATEGORY_DESC_PREFIX не установлена"
|
||||
log_message(error_msg, "ERROR")
|
||||
print(f"ОШИБКА: {error_msg}")
|
||||
return False
|
||||
|
||||
# Формирование URL
|
||||
desc_url = VOLK_CATEGORY_DESC_PREFIX + category_id
|
||||
log_message(f"URL для запроса: {desc_url}")
|
||||
log_message(f"Префикс URL: {VOLK_CATEGORY_DESC_PREFIX}")
|
||||
log_message(f"ID категории: {category_id}")
|
||||
|
||||
desc_response = None
|
||||
|
||||
# Шаг 1: Запрос через urlopen
|
||||
log_message("ШАГ 1: Вызов urlopen()")
|
||||
try:
|
||||
req = Request(desc_url)
|
||||
log_message(f"Request объект создан: {req}")
|
||||
safe_log_direct("Выполняется urlopen с таймаутом 30 секунд", "DEBUG")
|
||||
response = urlopen(req, timeout=30)
|
||||
safe_log_direct("urlopen успешно выполнен", "DEBUG")
|
||||
log_message("urlopen успешно выполнен")
|
||||
except (URLError, HTTPError, OSError, socket.gaierror, socket.timeout, ConnectionError) as e:
|
||||
# Используем безопасное логирование для критических ошибок
|
||||
safe_log_direct("ПЕРЕХВАЧЕНА СЕТЕВАЯ ОШИБКА urlopen", "ERROR")
|
||||
safe_log_direct(f"Категория: {category_id}, URL: {desc_url}", "ERROR")
|
||||
safe_log_direct(f"Тип ошибки: {type(e).__name__}, Сообщение: {str(e)}", "ERROR")
|
||||
try:
|
||||
traceback_str = traceback.format_exc()
|
||||
safe_log_direct(f"Трассировка:\n{traceback_str}", "ERROR")
|
||||
log_message(f"Трассировка сетевой ошибки:\n{traceback_str}", "ERROR")
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
error_msg = f"Ошибка сетевого подключения при запросе описания категории {category_id} из {desc_url}: {type(e).__name__}: {e}"
|
||||
log_message(error_msg, "ERROR")
|
||||
except:
|
||||
pass
|
||||
print(f"ОШИБКА: Сетевая ошибка при запросе - {type(e).__name__}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
# Перехватываем любые другие исключения, которые могут возникнуть при urlopen
|
||||
safe_log_direct("ПЕРЕХВАЧЕНА НЕОЖИДАННАЯ ОШИБКА urlopen", "ERROR")
|
||||
safe_log_direct(f"Категория: {category_id}, URL: {desc_url}", "ERROR")
|
||||
safe_log_direct(f"Тип ошибки: {type(e).__name__}, Сообщение: {str(e)}", "ERROR")
|
||||
try:
|
||||
traceback_str = traceback.format_exc()
|
||||
safe_log_direct(f"Трассировка:\n{traceback_str}", "ERROR")
|
||||
log_message(f"Трассировка неожиданной ошибки:\n{traceback_str}", "ERROR")
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
error_msg = f"Неожиданная ошибка при запросе описания категории {category_id} из {desc_url}: {type(e).__name__}: {e}"
|
||||
log_message(error_msg, "ERROR")
|
||||
except:
|
||||
pass
|
||||
print(f"ОШИБКА: Неожиданная ошибка - {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
# Шаг 2: Чтение и декодирование данных
|
||||
log_message("ШАГ 2: Чтение и декодирование данных")
|
||||
try:
|
||||
with response:
|
||||
# Явно указываем кодировку UTF-8 при декодировании
|
||||
raw_data = response.read()
|
||||
log_message(f"raw data read, размер: {len(raw_data)} байт")
|
||||
safe_log_direct(f"Размер raw_data: {len(raw_data)} байт", "DEBUG")
|
||||
|
||||
try:
|
||||
decoded_data = raw_data.decode('utf-8')
|
||||
log_message("data decoded (UTF-8)")
|
||||
safe_log_direct("Декодирование UTF-8 успешно", "DEBUG")
|
||||
except UnicodeDecodeError as e:
|
||||
error_msg = f"Ошибка декодирования ответа для категории {category_id}: {e}. Попытка декодирования как latin-1."
|
||||
log_message(error_msg, "WARNING")
|
||||
decoded_data = raw_data.decode('latin-1')
|
||||
log_message("data decoded (latin-1 fallback)")
|
||||
|
||||
log_message(f"Длина декодированной строки: {len(decoded_data)} символов")
|
||||
log_message(f"Первые 200 символов ответа:\n{decoded_data[:200]}")
|
||||
|
||||
# Шаг 3: Парсинг JSON
|
||||
log_message("ШАГ 3: Парсинг JSON")
|
||||
try:
|
||||
desc_response = json.loads(decoded_data)
|
||||
log_message("json loaded успешно")
|
||||
safe_log_direct("JSON успешно распарсен", "DEBUG")
|
||||
except json.JSONDecodeError as e:
|
||||
error_msg = f"Ошибка парсинга JSON для категории {category_id}: {e}. Первые 500 символов ответа: {decoded_data[:500]}"
|
||||
log_message(error_msg, "ERROR")
|
||||
log_message(f"Полный ответ (первые 1000 символов):\n{decoded_data[:1000]}", "ERROR")
|
||||
print(f"ОШИБКА: Не удалось распарсить JSON - {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
error_msg = f"Ошибка при чтении ответа для категории {category_id}: {type(e).__name__}: {e}"
|
||||
log_message(error_msg, "ERROR")
|
||||
traceback_str = traceback.format_exc()
|
||||
log_message(f"Трассировка ошибки чтения:\n{traceback_str}", "ERROR")
|
||||
print(f"ОШИБКА: Ошибка чтения ответа - {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
# Проверяем, что desc_response был успешно получен
|
||||
if desc_response is None:
|
||||
error_msg = f"Не удалось получить ответ для категории {category_id}"
|
||||
log_message(error_msg, "ERROR")
|
||||
print(f"ОШИБКА: {error_msg}")
|
||||
return False
|
||||
|
||||
log_message(f"Получен JSON ответ: {json.dumps(desc_response, ensure_ascii=False, indent=2)}")
|
||||
|
||||
# Шаг 4: Проверка версии API
|
||||
log_message("ШАГ 4: Проверка версии API")
|
||||
api_version = desc_response.get('api_version')
|
||||
log_message(f"Получена версия API: {api_version}")
|
||||
log_message(f"Ожидаемая версия API: {VOLK_API_VERSION}")
|
||||
|
||||
if api_version != VOLK_API_VERSION:
|
||||
error_msg = f"Несовпадение версии API при загрузке описания категории {category_id}: получена версия '{api_version}', ожидается '{VOLK_API_VERSION}'."
|
||||
log_message(error_msg, "ERROR")
|
||||
print(f"ОШИБКА: {error_msg}")
|
||||
return False
|
||||
|
||||
log_message("Версия API соответствует ожидаемой")
|
||||
|
||||
# Шаг 5: Проверка наличия блока data
|
||||
log_message("ШАГ 5: Проверка наличия блока data")
|
||||
if 'data' not in desc_response:
|
||||
error_msg = f"В JSON ответе для категории {category_id} отсутствует блок 'data'."
|
||||
log_message(error_msg, "ERROR")
|
||||
log_message(f"Структура JSON ответа: {list(desc_response.keys())}", "ERROR")
|
||||
print(f"ОШИБКА: {error_msg}")
|
||||
return False
|
||||
|
||||
log_message("Блок 'data' найден в ответе")
|
||||
|
||||
desc_data = desc_response['data']
|
||||
log_message(f"Тип desc_data: {type(desc_data)}")
|
||||
|
||||
# Шаг 6: Проверка типа desc_data
|
||||
log_message("ШАГ 6: Проверка типа desc_data")
|
||||
if not isinstance(desc_data, dict):
|
||||
error_msg = f"Блок 'data' для категории {category_id} не является словарём (тип: {type(desc_data)})."
|
||||
log_message(error_msg, "ERROR")
|
||||
print(f"ОШИБКА: {error_msg}")
|
||||
return False
|
||||
|
||||
log_message("desc_data является словарём")
|
||||
log_message(f"Ключи в desc_data: {list(desc_data.keys())}")
|
||||
|
||||
# Шаг 7: Получение description
|
||||
log_message("ШАГ 7: Получение description из блока data")
|
||||
description = desc_data.get('description')
|
||||
log_message(f"description получен, тип: {type(description)}")
|
||||
|
||||
if description:
|
||||
log_message(f"Длина description: {len(description)} символов")
|
||||
log_message(f"Первые 500 символов description:\n{description[:500]}")
|
||||
log_message("Описание успешно получено")
|
||||
else:
|
||||
log_message("description пустое или отсутствует (будет установлено в None)")
|
||||
description = None
|
||||
|
||||
# Итоговый результат
|
||||
if category_number and total_count:
|
||||
footer = f"ТЕСТИРОВАНИЕ ЗАВЕРШЕНО УСПЕШНО [{category_number}/{total_count}] КАТЕГОРИИ: {category_id}"
|
||||
else:
|
||||
footer = f"ТЕСТИРОВАНИЕ ЗАВЕРШЕНО УСПЕШНО ДЛЯ КАТЕГОРИИ: {category_id}"
|
||||
|
||||
log_message("=" * 80)
|
||||
log_message(footer)
|
||||
log_message("=" * 80)
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Главная функция"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Тестирование загрузки описания категории из API VOLK',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Примеры использования:
|
||||
python tools/dev/volk_category_testing.py volk26_nri
|
||||
python tools/dev/volk_category_testing.py volk26_nri volk26_other volk26_third
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
'category_ids',
|
||||
type=str,
|
||||
nargs='+',
|
||||
help='ID категорий для тестирования (от 1 до 10 штук, например: volk26_nri volk26_other)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
category_ids = args.category_ids
|
||||
|
||||
# Проверка количества ID
|
||||
if len(category_ids) == 0:
|
||||
print("ОШИБКА: Не указан ни один ID категории")
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
if len(category_ids) > 10:
|
||||
print(f"ОШИБКА: Указано слишком много ID категорий ({len(category_ids)}). Максимум 10.")
|
||||
sys.exit(1)
|
||||
|
||||
# Проверка наличия необходимых переменных окружения
|
||||
if not VOLK_CATEGORY_DESC_PREFIX:
|
||||
print("ОШИБКА: Переменная окружения VOLK_CATEGORY_DESC_PREFIX не установлена")
|
||||
print("Проверьте файл .env")
|
||||
sys.exit(1)
|
||||
|
||||
if not VOLK_API_VERSION:
|
||||
print("ОШИБКА: Переменная окружения VOLK_API_VERSION не установлена")
|
||||
print("Проверьте файл .env")
|
||||
sys.exit(1)
|
||||
|
||||
log_message("=" * 80)
|
||||
log_message("ЗАПУСК ТЕСТИРОВАНИЯ ЗАГРУЗКИ ОПИСАНИЯ КАТЕГОРИЙ")
|
||||
log_message(f"Время запуска: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
log_message(f"Количество категорий для обработки: {len(category_ids)}")
|
||||
log_message(f"ID категорий: {', '.join(category_ids)}")
|
||||
log_message(f"Префикс URL: {VOLK_CATEGORY_DESC_PREFIX}")
|
||||
log_message(f"Версия API: {VOLK_API_VERSION}")
|
||||
log_message(f"Лог-файл: {TEST_LOG_FILE}")
|
||||
log_message("=" * 80)
|
||||
|
||||
# Обработка каждой категории последовательно
|
||||
total_count = len(category_ids)
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for index, category_id in enumerate(category_ids, start=1):
|
||||
log_message("")
|
||||
log_message("")
|
||||
log_message(f">>> ОБРАБОТКА КАТЕГОРИИ {index} из {total_count}: {category_id}")
|
||||
log_message("")
|
||||
|
||||
print(f"\n[{index}/{total_count}] Обработка категории: {category_id}")
|
||||
|
||||
success = test_category_description_load(category_id, category_number=index, total_count=total_count)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
print(f"[{index}/{total_count}] ✓ УСПЕХ: Категория {category_id} обработана успешно")
|
||||
else:
|
||||
fail_count += 1
|
||||
print(f"[{index}/{total_count}] ✗ ОШИБКА: Категория {category_id} не обработана")
|
||||
|
||||
# Итоговая статистика
|
||||
log_message("")
|
||||
log_message("")
|
||||
log_message("=" * 80)
|
||||
log_message("ИТОГОВАЯ СТАТИСТИКА")
|
||||
log_message("=" * 80)
|
||||
log_message(f"Всего категорий обработано: {total_count}")
|
||||
log_message(f"Успешно: {success_count}")
|
||||
log_message(f"С ошибками: {fail_count}")
|
||||
log_message(f"Время завершения: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
log_message("=" * 80)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"ИТОГИ:")
|
||||
print(f" Всего категорий: {total_count}")
|
||||
print(f" Успешно: {success_count}")
|
||||
print(f" С ошибками: {fail_count}")
|
||||
print(f" Лог сохранен в: {TEST_LOG_FILE}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Возвращаем код ошибки, если были неудачи
|
||||
if fail_count > 0:
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user