rev 4
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
@@ -79,6 +80,13 @@ ERROR_SALUTE_UPLOAD_FAILED = "Ошибка загрузки файла в Salute
|
||||
ERROR_SALUTE_TASK_FAILED = "Ошибка распознавания SaluteSpeech: {detail}"
|
||||
ERROR_SALUTE_TIMEOUT = "Превышено время ожидания распознавания SaluteSpeech"
|
||||
ERROR_SALUTE_UNSUPPORTED_FORMAT = "Неподдерживаемый формат аудио для SaluteSpeech: {ext}"
|
||||
ERROR_SALUTE_AUTH_KEY_MISSING = "Не задан SALUTE_AUTH_KEY для авторизации SaluteSpeech"
|
||||
ERROR_SALUTE_SSL_FAILED = (
|
||||
"SSL ошибка при обращении к SaluteSpeech: {detail}. "
|
||||
"Если используется корпоративный или локальный сертификат, "
|
||||
"укажите путь в SALUTE_CA_CERT_PATH. "
|
||||
"Для локальной отладки можно отключить проверку через SALUTE_SSL_VERIFY=false."
|
||||
)
|
||||
|
||||
_salute_token_lock = threading.Lock()
|
||||
_salute_token: str | None = None
|
||||
@@ -487,6 +495,27 @@ def _parse_salute_time(s: str) -> float:
|
||||
return float(s.rstrip("s"))
|
||||
|
||||
|
||||
def _build_salute_ssl_context() -> ssl.SSLContext:
|
||||
"""Build SSL context for SaluteSpeech using system trust plus optional custom CA."""
|
||||
settings = get_settings()
|
||||
if not settings.salute_ssl_verify:
|
||||
return ssl._create_unverified_context()
|
||||
|
||||
ssl_context = ssl.create_default_context()
|
||||
if settings.salute_ca_cert_path is not None:
|
||||
ssl_context.load_verify_locations(cafile=str(settings.salute_ca_cert_path))
|
||||
return ssl_context
|
||||
|
||||
|
||||
def _get_salute_auth_header_value() -> str:
|
||||
"""Build Basic auth header for SaluteSpeech from settings."""
|
||||
settings = get_settings()
|
||||
auth_key = settings.salute_auth_key.strip()
|
||||
if not auth_key:
|
||||
raise RuntimeError(ERROR_SALUTE_AUTH_KEY_MISSING)
|
||||
return f"Basic {auth_key}"
|
||||
|
||||
|
||||
def _get_salute_access_token(client: httpx.Client) -> str:
|
||||
"""Get or refresh SaluteSpeech OAuth token. Thread-safe."""
|
||||
global _salute_token, _salute_token_expires_at
|
||||
@@ -500,7 +529,7 @@ def _get_salute_access_token(client: httpx.Client) -> str:
|
||||
response = client.post(
|
||||
SALUTE_AUTH_URL,
|
||||
headers={
|
||||
"Authorization": f"Basic {settings.salute_auth_key}",
|
||||
"Authorization": _get_salute_auth_header_value(),
|
||||
"RqUID": str(uuid.uuid4()),
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
@@ -708,8 +737,6 @@ def _salute_transcribe_sync(
|
||||
on_progress: ProgressCallback | None = None,
|
||||
) -> Document:
|
||||
"""Synchronous SaluteSpeech transcription (runs in Dramatiq worker thread)."""
|
||||
settings = get_settings()
|
||||
|
||||
ext = Path(local_file_path).suffix.lower()
|
||||
audio_encoding = SALUTE_ENCODING_MAP.get(ext)
|
||||
content_type = SALUTE_CONTENT_TYPE_MAP.get(ext)
|
||||
@@ -725,8 +752,8 @@ def _salute_transcribe_sync(
|
||||
salute_language = SALUTE_LANGUAGE_MAP.get(language or "", "ru-RU")
|
||||
|
||||
try:
|
||||
verify = str(settings.salute_ca_cert_path) if settings.salute_ca_cert_path else True
|
||||
with httpx.Client(verify=verify, timeout=30.0) as client:
|
||||
ssl_context = _build_salute_ssl_context()
|
||||
with httpx.Client(verify=ssl_context, timeout=30.0) as client:
|
||||
token = _get_salute_access_token(client)
|
||||
|
||||
with open(local_file_path, "rb") as f:
|
||||
@@ -748,6 +775,14 @@ def _salute_transcribe_sync(
|
||||
raw_result = _download_salute_result(client, token, response_file_id)
|
||||
|
||||
return _build_document_from_salute_result(raw_result, language=salute_language)
|
||||
except ssl.SSLError as exc:
|
||||
raise RuntimeError(ERROR_SALUTE_SSL_FAILED.format(detail=str(exc))) from exc
|
||||
except httpx.ConnectError as exc:
|
||||
if isinstance(exc.__cause__, ssl.SSLError):
|
||||
raise RuntimeError(
|
||||
ERROR_SALUTE_SSL_FAILED.format(detail=str(exc.__cause__))
|
||||
) from exc
|
||||
raise
|
||||
finally:
|
||||
if cleanup_fn is not None:
|
||||
cleanup_fn()
|
||||
|
||||
Reference in New Issue
Block a user