# Data Model: Async Backend Refactoring **Feature**: `032-translate-requests-httpx` **Date**: 2026-06-04 **Status**: ✅ Implemented ## Overview Рефакторинг **не добавляет новых бизнес-таблиц** (SQLAlchemy models) и **не меняет публичные request/response Pydantic-схемы**. Добавляются транспортные/runtime-сущности (`SupersetClientRegistry`, `run_blocking`, bounded executors) и расширяется конфигурация окружений (`EnvironmentConfig`) и приложения (`AppAsyncRuntimeConfig`). Требование миграции БД зависит от формата хранения `EnvironmentConfig`: если JSON/Pydantic blob — defaults через `Field(default=...)` достаточны; если SQL‑колонки — нужна Alembic-миграция. ## 1. Changed Entities (Transport Layer) ### AsyncAPIClient (replaces APIClient) | Attribute | Type | Change | |-----------|------|--------| | `session` | `requests.Session` → `httpx.AsyncClient` | Замена sync клиента на async | | `base_url` | `str` | Без изменений | | `csrf_token` | `str` | Без изменений (кешируется) | | `semaphore` | **NEW** `asyncio.Semaphore` | Ограничение конкурентных соединений | | `timeout` | **NEW** `httpx.Timeout` | Настраиваемый таймаут (default: 30s) | **Lifecycle**: Клиент создаётся через `async with AsyncAPIClient(env_config) as client:` — контекстный менеджер управляет жизненным циклом `httpx.AsyncClient` (открытие/закрытие connection pool). ### SupersetClient (merged sync + async) | Attribute | Type | Change | |-----------|------|--------| | `network` | `APIClient` → `AsyncAPIClient` | Замена sync на async | | All mixin methods | `def` → `async def` | Все 13 миксинов становятся async | **Methods (typical signature change)**: ```python # Before (sync) def get_dashboards(self, query=None) -> tuple[int, list[dict]]: ... # After (async) async def get_dashboards(self, query=None) -> tuple[int, list[dict]]: ... ``` ### SupersetSqlLabExecutor | Attribute | Type | Change | |-----------|------|--------| | `_client` | `SupersetClient` (sync) | Замена на async | | `execute_sql()` | `def` → `async def` | Асинхронный HTTP + поллинг | | `poll_execution_status()` | `def` → `async def` | `time.sleep` → `asyncio.sleep` | | `max_polls` | `int` (60) | Без изменений | | `poll_interval_seconds` | `float` (2.0) | Без изменений | ### TaskManager | Attribute | Type | Change | |-----------|------|--------| | `executor` | `ThreadPoolExecutor` | **Удалён** | | `_tasks` | `dict[str, asyncio.Task]` | **NEW** — словарь активных asyncio.Task | | `run_task()` | `def` → `async def` | Запуск через `asyncio.create_task` | | `cancel_task()` | `def` → `async def` | Отмена через `task.cancel()` | ### EventBus | Attribute | Type | Change | |-----------|------|--------| | `_queue` | `queue.Queue` → `asyncio.Queue` | Замена sync очереди на async | | `publish()` | `def` → `async def` | `put_nowait` → `await put` | | `subscribe()` | `def` → `async def` | `get` → `await get` | ### LLM Client (unified) | Attribute | Type | Change | |-----------|------|--------| | `call_openai_compatible()` | `def` → `async def` | Асинхронный HTTP | | `_do_http_request()` | `requests.post` + `time.sleep` | `httpx.AsyncClient.post` + `asyncio.sleep` | | `timeout` | `int` (180) → `httpx.Timeout(180)` | Настраиваемый таймаут | ### Notification Providers | Provider | Method | Change | |----------|--------|--------| | SMTPProvider | `send()` | `smtplib.SMTP` → `aiosmtplib.SMTP` | | TelegramProvider | `send()` | `requests.post` → `httpx.AsyncClient.post` | | SlackProvider | `send()` | `requests.post` → `httpx.AsyncClient.post` | ## 2. New Entities ### SupersetClientRegistry [C:4] Синглтон-реестр долгоживущих асинхронных клиентов по `env_id`: | Attribute | Type | Description | |-----------|------|-------------| | `_clients` | `dict[str, _ClientSlot]` | env_id → slot с shared ресурсами | | `_ClientSlot.client` | `httpx.AsyncClient` | Долгоживущий HTTP-клиент (connection pooling) | | `_ClientSlot.semaphore` | `asyncio.Semaphore` | Глобальный per‑env семафор (connection_pool_size) | | `_ClientSlot.auth_lock` | `asyncio.Lock` | Защита конкурентного обновления CSRF/auth‑токенов | **Lifecycle**: Клиенты создаются при первом обращении к `env_id`, живут до shutdown. `async with` — только для тестов. ### DB Threading Contract SQLAlchemy‑сессии создаются, используются, коммитятся/откатываются и закрываются **внутри одного worker‑потока** (через `asyncio.to_thread(db_executor, ...)`). Никакие ORM‑объекты не пересекают границу потока без явного detach/serialization. DB‑адаптер владеет жизненным циклом сессии. ## 3. Configuration Parameters ### Environment Config (per Superset instance — `EnvironmentConfig`) ```json { "connection_pool_size": 20, "connection_pool_timeout": 30, "request_timeout": 30, "llm_timeout": 120 } ``` ### App-Level Async Runtime Config (global — отдельная секция конфигурации приложения, не per‑env) ```json { "db_executor_workers": 10, "file_executor_workers": 10, "git_executor_workers": 5, "graceful_shutdown_timeout": 30 } ``` Эти параметры добавляются в модель `EnvironmentConfig` (Pydantic) и хранятся в БД как часть конфигурации окружения. ## 3. Unchanged Entities Следующие сущности **не меняют** свою структуру, только способ взаимодействия с внешними сервисами: | Entity | Type | Notes | |--------|------|-------| | `TranslationJob` | SQLAlchemy Model | Без изменений | | `TranslationPreviewSession` | SQLAlchemy Model | Без изменений | | `TranslationPreviewRecord` | SQLAlchemy Model | Без изменений | | `TranslationPreviewLanguage` | SQLAlchemy Model | Без изменений | | `Dictionary` | SQLAlchemy Model | Без изменений | | `LLMProvider` | SQLAlchemy Model | Без изменений | | `EnvironmentConfig` | SQLAlchemy Model | Добавлены pool-параметры (см. выше) | | Все Pydantic request/response schemas | Pydantic | Без изменений (FR-018) | | Все API routes (URL, method, status codes) | FastAPI Router | Без изменений | | `PluginBase.execute()` contract | ABC | Уже `async def`, без изменений | ## 4. Task State Transitions (Unchanged) ``` PENDING → RUNNING → COMPLETED → FAILED → CANCELLED ``` Переходы состояний не меняются. Меняется только механизм запуска: `ThreadPoolExecutor.submit()` → `asyncio.create_task()`. ## 5. WebSocket Message Format (Unchanged) ```json { "type": "log", "task_id": "uuid", "timestamp": "ISO-8601", "level": "INFO|WARNING|ERROR", "message": "...", "trace_id": "uuid" } ``` Формат сообщений WebSocket не меняется. Меняется только транспорт отправки: `queue.Queue` + `call_soon_threadsafe` → `asyncio.Queue` + `await`.