7.3 KiB
Implementation Plan: Async Backend Refactoring
Branch: 032-translate-requests-httpx | Date: 2026-06-04 | Spec: spec.md
Input: Feature specification from /specs/032-translate-requests-httpx/spec.md
Summary
Полный рефакторинг 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)
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)
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 для конкурентности |