Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
+176
View File
@@ -0,0 +1,176 @@
"""Фоновая инициализация: миграции Alembic и регистрация API после старта HTTP-сервера."""
from __future__ import annotations
import logging
import os
import threading
from typing import Callable, List, Optional
from flask import Flask
from app.services.startup_state import (
mark_startup_failed,
mark_startup_ready,
reset_startup_state,
set_startup_phase,
)
_log = logging.getLogger(__name__)
_post_ready_callbacks: List[Callable[[Flask], None]] = []
_startup_thread_started = False
_startup_thread_lock = threading.Lock()
def register_post_ready_callback(fn: Callable[[Flask], None]) -> None:
_post_ready_callbacks.append(fn)
def is_reloader_parent_process(app: Flask) -> bool:
"""Родитель Flask reloader (watchdog): не трогает БД, запросы не обслуживает."""
if not app.debug:
return False
if os.environ.get("WERKZEUG_RUN_MAIN") is not None:
return False
try:
from werkzeug.serving import is_running_from_reloader
if is_running_from_reloader():
return False
except ImportError:
pass
return True
def should_run_background_startup(app: Flask) -> bool:
"""Фоновый поток миграций — один раз на рабочий процесс."""
if app.config.get("TESTING"):
return False
if os.environ.get("WESP_ALEMBIC", "").strip() == "1":
return False
if not app.config.get("WESP_BACKGROUND_STARTUP", True):
return False
if is_reloader_parent_process(app):
return False
return True
def _finish_app_startup(app: Flask) -> None:
"""После миграций: отложенные фоновые службы (маршруты sklad — в create_app)."""
set_startup_phase("services", "Подключение модулей…")
if app.config.get("SYNC_BACKGROUND_REQUEUE", True):
from app.services.startup_defer import schedule_sync_requeue_after_boot
schedule_sync_requeue_after_boot(app)
try:
import psutil # noqa: F401
psutil.cpu_percent(interval=None)
except Exception:
_log.debug("psutil: прогрев при старте", exc_info=True)
try:
from app.services.startup_defer import schedule_mdns_after_boot
schedule_mdns_after_boot(app)
except Exception:
_log.warning("mDNS не запланирован при старте", exc_info=True)
from app.services.startup_defer import schedule_bootstrap_update_env
schedule_bootstrap_update_env(app)
if not app.config.get("TESTING"):
try:
from app.lab.commands.seed_demo_component_nutrients import seed_demo_component_nutrients_once
seed_demo_component_nutrients_once()
except Exception:
_log.debug("Разовый seed nutrients компонентов", exc_info=True)
def run_blocking_startup_tasks(app: Flask) -> None:
"""Синхронные миграции + догонка схемы + finish (для тестов и режима без фона)."""
from app import _run_auto_migrations
if not app.config.get("TESTING"):
try:
from config import repair_sync_client_state_on_disk
repair_sync_client_state_on_disk()
except Exception:
_log.debug("repair_sync_client_state_on_disk", exc_info=True)
_run_auto_migrations(app)
_finish_app_startup(app)
mark_startup_ready()
def _background_startup_worker(app: Flask) -> None:
reset_startup_state()
set_startup_phase("migrations", "Миграции базы данных…")
try:
with app.app_context():
if not app.config.get("TESTING"):
try:
from config import repair_sync_client_state_on_disk
repair_sync_client_state_on_disk()
except Exception:
_log.debug("repair_sync_client_state_on_disk", exc_info=True)
from app import _run_auto_migrations
_run_auto_migrations(app)
_finish_app_startup(app)
mark_startup_ready("Система готова")
for cb in list(_post_ready_callbacks):
try:
cb(app)
except Exception:
_log.exception("post-ready callback failed")
except Exception as exc:
_log.exception("Фоновая инициализация WESP не удалась")
mark_startup_failed(exc)
def schedule_background_startup(app: Flask) -> bool:
"""Запускает поток инициализации один раз на процесс. Возвращает True, если поток стартовал."""
global _startup_thread_started
if is_reloader_parent_process(app):
_log.info(
"Старт reloader (родитель): миграции пропущены — инициализация в дочернем процессе"
)
return False
if not should_run_background_startup(app):
if not is_startup_ready_implicit(app):
run_blocking_startup_tasks(app)
return False
with _startup_thread_lock:
if _startup_thread_started:
return False
_startup_thread_started = True
reset_startup_state()
set_startup_phase("migrations", "Миграции базы данных…")
threading.Thread(
target=_background_startup_worker,
args=(app,),
name="wesp-background-startup",
daemon=True,
).start()
return True
def is_startup_ready_implicit(app: Flask) -> bool:
from app.services.startup_state import is_startup_ready
if is_startup_ready():
return True
if not app.config.get("WESP_DEFERRED_STARTUP"):
return True
return False