# Implementation Plan: Async Backend Refactoring **Branch**: `032-translate-requests-httpx` | **Date**: 2026-06-04 | **Spec**: [spec.md](./spec.md) | **Status**: ✅ Implemented **Input**: Feature specification from `/specs/032-translate-requests-httpx/spec.md` ## Status **Реализовано**. Полный рефакторинг backend на асинхронный неблокирующий ввод-вывод. Полный рефакторинг backend на асинхронный неблокирующий ввод-вывод. Big-bang замена синхронного `requests.Session` на `httpx.AsyncClient` в центральном `APIClient`, слияние sync `SupersetClient` и `AsyncSupersetClient` в единый асинхронный клиент, перевод всех 13 миксинов SupersetClient на async, замена `time.sleep` на `asyncio.sleep` во всех поллинг-циклах, обёртка блокирующих операций БД и файлового I/O в `run_blocking` (bounded executors), полный переход Task Manager'а с `ThreadPoolExecutor` на `asyncio.create_task`. Фронтенд не меняется. ## Technical Context **Language/Version**: Python 3.9+ (backend, canonical — ADR-0001 note: Python 3.13+ preferred but 3.9+ is the minimum supported), TypeScript (frontend Svelte 5 runes-only) **Primary Dependencies**: FastAPI 0.126, SQLAlchemy, APScheduler 3.11, httpx 0.28 (already present), anyio 4.12 (already present) **New Dependencies**: pytest-httpx (test mocking), aiofiles (optional — file I/O), aiosmtplib (optional — SMTP) **Storage**: PostgreSQL 16 (unchanged — DB operations via asyncio.to_thread) **Testing**: pytest (backend), pytest-httpx (new), pytest-asyncio (existing) **Target Platform**: Linux server (Docker), single uvicorn worker **Project Type**: web application backend (FastAPI REST + WebSocket) **Performance Goals**: <100ms p95 для лёгких запросов во время тяжёлых операций; конкурентность 3+ тяжёлых запросов при 1 worker **Constraints**: API-контракты неизменны; все тесты проходят; нулевые утечки соединений; обратная совместимость WebSocket-логирования **Scale/Scope**: ~16 файлов, ~45 блокирующих вызовов, 1 uvicorn worker, 20 одновременных соединений к Superset (семафор) ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | # | Principle | Status | Justification | |---|-----------|--------|---------------| | I | Semantic Contract First | ✅ PASS | Все изменённые функции получат/обновят C3-C4 контракты. Новый `AsyncAPIClient` — C4 с @PRE/@POST/@SIDE_EFFECT. | | II | Decision Memory | ✅ PASS | ADR-0011 будет создан с записью rejected-путей (aiohttp, multiprocessing, async SQLAlchemy). @RATIONALE/@REJECTED в каждом C4+ контракте. | | III | External Orchestrator | ✅ PASS | Топология не меняется. ss-tools остаётся внешним оркестратором над Superset. | | IV | Module Discipline | ✅ PASS | `network.py` (542 строки) → Tombstone. `async_network.py` (<400 строк) — canonical async client, расширяется семафором. `SupersetAuthCache` уже выделен. | | V | RBAC Enforcement | ✅ PASS | Проверки прав не затрагиваются. Ручки сохраняют `Depends(has_permission(...))`. | | VI | Frontend Svelte 5 Runes Only | ✅ N/A | Фронтенд не меняется. | | VII | Test-Driven for C3+ | ✅ PASS | Тесты на конкурентность (asyncio.gather + pytest_httpx) написаны для всех C3+ контрактов. | ## Project Structure ### Documentation (this feature) ```text specs/032-translate-requests-httpx/ ├── plan.md # This file ├── research.md # Phase 0 output ├── data-model.md # Phase 1 output ├── quickstart.md # Phase 1 output ├── contracts/ # Phase 1 output │ └── modules.md # GRACE contracts for new/changed modules └── tasks.md # Phase 2 output (/speckit.tasks) ``` ### Source Code (repository root — files touched) ```text backend/ ├── src/ │ ├── core/ │ │ ├── utils/ │ │ │ ├── network.py → Tombstone (sync APIClient replaced) │ │ │ └── async_network.py → extended: semaphore + pool config added │ │ │ ├── fileio.py → async wrappers for file operations │ │ │ └── superset_compilation_adapter.py → async migration │ │ ├── superset_client/ │ │ │ ├── _base.py → merged with AsyncSupersetClient │ │ │ ├── async_superset_client.py → absorbed into _base.py │ │ │ └── _mixins/*.py → all 13 mixins become async │ │ └── task_manager/ │ │ ├── manager.py → ThreadPoolExecutor → asyncio.create_task │ │ ├── lifecycle.py → async lifecycle │ │ └── event_bus.py → async WebSocket logging │ ├── api/routes/ │ │ ├── dashboards/_detail_routes.py → async SupersetClient │ │ └── translate/*.py → already async def, now truly non-blocking │ ├── plugins/ │ │ ├── translate/ │ │ │ ├── _llm_http.py → httpx.AsyncClient │ │ │ ├── preview_llm_client.py → httpx.AsyncClient │ │ │ ├── superset_executor.py → async execute + asyncio.sleep │ │ │ ├── _run_source.py → async chart data fetch │ │ │ └── preview_executor.py → async sample row fetch │ │ ├── backup.py → async SupersetClient + file I/O │ │ ├── git_plugin.py → asyncio.to_thread for shutil + git │ │ ├── llm_analysis/plugin.py → async dashboard logs fetch │ │ └── storage/plugin.py → asyncio.to_thread for file ops │ └── services/ │ ├── notifications/providers.py → aiosmtplib + httpx │ └── git/_base.py → asyncio.to_thread for git/file ops └── tests/ → pytest_httpx replaces requests_mock ``` ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | `network.py` > 400 строк (542) | Центральный HTTP-клиент со всей логикой аутентификации, пагинации, загрузки файлов | `network.py` → Tombstone. `async_network.py` (уже существует, <400 строк) — canonical async client. `SupersetAuthCache` уже выделен и переиспользуется обоими клиентами. | | Task Manager рефакторинг | Переход с ThreadPoolExecutor на asyncio.create_task требует изменения lifecycle и event_bus | Оставить ThreadPoolExecutor — rejected, т.к. задачи должны разделять event loop для конкурентности |