- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
99 lines
7.5 KiB
Markdown
99 lines
7.5 KiB
Markdown
# 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 | Топология не меняется. superset-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 для конкурентности |
|