# План: Decouple polygon v0.2.1 Цель: разнести монолитный `app.py` (400 строк, 17 роутов) на отдельные blueprint-файлы по шаблону app-autotest. Никакого инлайн-CSS/HTML — всё в `static/` и `templates/`. ## Текущее → Целевое ``` polygon/site/ ├── app.py (400 строк, всё в одном) ├── mock_state.py ├── state_machine.py ├── config_loader.py └── from_stands.py ↓ polygon/site/ ├── app.py # ТОЛЬКО: Flask(), register_blueprint, /health, app.run() ├── routes/ │ ├── root.py # GET / (render_template + style.css) │ ├── services_routes.py # GET /api/v1/svc/services, GET /services/ │ ├── instances_routes.py # GET/POST /api/v1/svc/instances, GET /instances/ │ ├── operations_routes.py # POST /instanceOperations, GET default, GET status, POST params, GET validate │ ├── run.py # POST /instanceOperations//run │ └── mock_routes.py # POST /_mock/reset, GET /_mock/state, GET /_mock/services, POST /_mock/delay ├── state/ │ ├── mock_state.py # MockState (без изменений) │ └── state_machine.py # apply_effect (без изменений) ├── config/ │ └── loader.py # load_services() (бывший config_loader.py) ├── converter/ │ └── from_stands.py # конвертер (без изменений) ├── utils/ │ ├── pluralize.py # _pluralize() — одна функция │ └── now.py # _now() — одна функция ├── static/ │ └── style.css # CSS из index() — тёмная тема ├── templates/ │ └── index.html # HTML из index() — Jinja2 с {{ VERSION }} └── services/ └── ...yaml # без изменений ``` ## Пошагово ### Шаг 1. Утилиты - `utils/now.py` — `_now()` из app.py (одна функция) - `utils/pluralize.py` — `_pluralize()` из state_machine.py (одна функция) ### Шаг 2. CSS и HTML - `static/style.css` — вынести инлайн-CSS из f-строки `index()` - `templates/index.html` — вынести HTML из f-строки, использовать Jinja2 `{{ version }}`, `` ### Шаг 3. Маршруты — 6 blueprint-файлов Каждый blueprint импортирует нужные модули из `state/`, `config/`, `utils/`. - `routes/root.py` — `bp = Blueprint("root", __name__)`, роут `/` с `render_template` - `routes/services_routes.py` — `bp = Blueprint("services", __name__)`, 2 роута - `routes/instances_routes.py` — `bp = Blueprint("instances", __name__)`, 3 роута - `routes/operations_routes.py` — `bp = Blueprint("operations", __name__)`, 5 роутов - `routes/run.py` — `bp = Blueprint("run", __name__)`, 1 роут - `routes/mock_routes.py` — `bp = Blueprint("mock", __name__)`, 4 роута ### Шаг 4. app.py — только скелет ```python import os from flask import Flask from routes.root import bp as root_bp from routes.services_routes import bp as services_bp # ... все 6 blueprint'ов from config.loader import load_services os.environ.setdefault("WEB_CONCURRENCY", "1") VERSION = "0.2.1" MOCK_OP_DELAY = float(os.getenv("MOCK_OP_DELAY", "0.1")) app = Flask(__name__, template_folder="templates", static_folder="static") app.register_blueprint(root_bp) app.register_blueprint(services_bp) # ... все 6 @app.route("/health") def health(): return "OK" if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=5000) ``` ### Шаг 5. Импорты в state_machine и from_stands - `state_machine.py`: `from utils.pluralize import pluralize` (вместо `_pluralize()` внутри) - `from_stands.py`: импортирует `pluralize` из `utils/` ## Что НЕ меняется - `state/mock_state.py` — как есть - `state/state_machine.py` — только импорт `pluralize` - `converter/from_stands.py` — только импорт `pluralize` - `services/*.yaml` — данные - `tests/` — только поправить пути импорта (site → site.state и т.д.) ## Верификация 1. `python -m py_compile` на ВСЕХ .py файлах 2. `python -c "from app import app; [print(r.rule) for r in app.url_map.iter_rules()]"` → те же 17 роутов 3. `pytest tests/ -v` → 19/19 PASS 4. Запустить `python app.py` → curl /health, /services, /instances → работает