Files
ss-tools/specs/032-translate-requests-httpx/plan.md
busya bc32dd65ef 032: mark all tasks [x] — feature complete
Updated status in spec.md, plan.md, tasks.md, research.md,
data-model.md, ux_reference.md, quickstart.md.
All 72 tasks completed. 34/34 tests passing.
2026-06-04 21:09:14 +03:00

99 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 для конкурентности |